最後活躍 1789532044

Customization of synth-shell for personal use

修訂 46f543306d7806a0b150dba85e54742458a9a406

shell-prompt.sh 原始檔案
1#!/usr/bin/env bash
2
3##
4##
5## Visit for instructions and more information:
6## https://github.com/andresgongora/synth-shell/
7##
8##
9
10##
11##==================================================================================================
12## CODE PARSERS
13##==================================================================================================
14##------------------------------------------------------------------------------
15##
16get8bitCode()
17{
18 CODE=$1
19 case $CODE in
20 default) echo 9 ;;
21 none) echo 9 ;;
22 black) echo 0 ;;
23 red) echo 1 ;;
24 green) echo 2 ;;
25 yellow) echo 3 ;;
26 blue) echo 4 ;;
27 magenta|purple|pink) echo 5 ;;
28 cyan) echo 6 ;;
29 light-gray) echo 7 ;;
30 dark-gray) echo 60 ;;
31 light-red) echo 61 ;;
32 light-green) echo 62 ;;
33 light-yellow) echo 63 ;;
34 light-blue) echo 64 ;;
35 light-magenta|light-purple) echo 65 ;;
36 light-cyan) echo 66 ;;
37 white) echo 67 ;;
38 *) echo 0 ;;
39 esac
40}
41
42getColorCode()
43{
44 COLOR=$1
45 if [ "$COLOR" -eq "$COLOR" ] 2>/dev/null; then
46 if [ "$COLOR" -gt 0 ] && [ "$COLOR" -lt 256 ]; then
47 echo "38;5;$COLOR"
48 else
49 echo 0
50 fi
51 else
52 BITCODE=$(get8bitCode "$COLOR")
53 COLORCODE=$((BITCODE + 30))
54 echo "$COLORCODE"
55 fi
56}
57
58getBackgroundCode()
59{
60 COLOR=$1
61 if [ "$COLOR" -eq "$COLOR" ] 2>/dev/null; then
62 if [ "$COLOR" -gt 0 ] && [ "$COLOR" -lt 256 ]; then
63 echo "48;5;$COLOR"
64 else
65 echo 0
66 fi
67 else
68 BITCODE=$(get8bitCode "$COLOR")
69 COLORCODE=$((BITCODE + 40))
70 echo "$COLORCODE"
71 fi
72}
73
74getEffectCode()
75{
76 EFFECT=$1
77 NONE=0
78 case $EFFECT in
79 none|default) echo "$NONE" ;;
80 bold|bright) echo 1 ;;
81 dim) echo 2 ;;
82 underline) echo 4 ;;
83 blink) echo 5 ;;
84 reverse) echo 7 ;;
85 hidden) echo 8 ;;
86 strikeout) echo 9 ;;
87 *) echo "$NONE" ;;
88 esac
89}
90
91getFormattingSequence()
92{
93 START='\e[0;'
94 MIDLE=$1
95 END='m'
96 echo -n "$START$MIDLE$END"
97}
98
99##==================================================================================================
100## AUX
101##==================================================================================================
102applyCodeToText()
103{
104 local RESET
105 RESET=$(getFormattingSequence "$(getEffectCode none)")
106 TEXT=$1
107 CODE=$2
108 echo -n "$CODE$TEXT$RESET"
109}
110
111##==================================================================================================
112## MAIN FUNCTIONS
113##==================================================================================================
114getFormatCode()
115{
116 local RESET
117 RESET=$(getFormattingSequence "$(getEffectCode none)")
118
119 if [ "$#" -eq 0 ]; then
120 echo -n "$RESET"
121 elif [ "$#" -eq 1 ]; then
122 TEXT_COLOR=$(getFormattingSequence "$(getColorCode "$1")")
123 echo -n "$TEXT_COLOR"
124 else
125 FORMAT=""
126 while [ "$1" != "" ]; do
127 TYPE=$1
128 ARGUMENT=$2
129 case $TYPE in
130 -c) CODE=$(getColorCode "$ARGUMENT") ;;
131 -b) CODE=$(getBackgroundCode "$ARGUMENT") ;;
132 -e) CODE=$(getEffectCode "$ARGUMENT") ;;
133 *) CODE="" ;;
134 esac
135
136 if [ "$FORMAT" != "" ]; then
137 FORMAT="$FORMAT;"
138 fi
139 FORMAT="$FORMAT$CODE"
140 shift
141 shift
142 done
143
144 FORMAT_CODE=$(getFormattingSequence "$FORMAT")
145 echo -n "${FORMAT_CODE}"
146 fi
147}
148
149formatText()
150{
151 local RESET
152 RESET=$(getFormattingSequence "$(getEffectCode none)")
153
154 if [ "$#" -eq 0 ]; then
155 echo -n "${RESET}"
156 elif [ "$#" -eq 1 ]; then
157 TEXT=$1
158 echo -n "${TEXT}${RESET}"
159 else
160 TEXT=$1
161 FORMAT_CODE=$(getFormatCode "${@:2}")
162 applyCodeToText "$TEXT" "$FORMAT_CODE"
163 fi
164}
165
166removeColorCodes()
167{
168 printf "%s" "$1" | sed 's/\x1b\[[0-9;]*m//g'
169}
170
171##==============================================================================
172## MAIN FORMAT
173##==============================================================================
174format="USER HOST PWD GIT PYENV TF KUBE DATE CLOCK"
175separator_char='\uE0B0'
176separator_padding_left=''
177separator_padding_right=''
178segment_padding=' '
179enable_vertical_padding=true
180enable_command_on_new_line=true
181
182# Nerd Font icons.
183# Font hanya perlu tersedia pada terminal/client yang merender sesi SSH
184# (misalnya Termius). Tidak perlu install Nerd Font di setiap server Debian.
185icon_user=$'\uf007'
186icon_folder=$'\uf07b'
187icon_calendar=$'\uf073'
188icon_clock=$'\uf017'
189icon_debian=$'\ue77d'
190icon_linux=$'\uf17c'
191
192##==============================================================================
193## USER
194##==============================================================================
195font_color_user="black"
196background_user="green"
197texteffect_user="bold"
198
199##==============================================================================
200## HOST
201##==============================================================================
202font_color_host="white"
203background_host="light-blue"
204texteffect_host="bold"
205
206##==============================================================================
207## PWD (working dir)
208##==============================================================================
209font_color_pwd="dark-gray"
210background_pwd="white"
211texteffect_pwd="bold"
212max_pwd_char="25"
213
214##==============================================================================
215## GIT
216##==============================================================================
217font_color_git="light-gray"
218background_git="dark-gray"
219texteffect_git="bold"
220git_symbol_synced=''
221git_symbol_unpushed=' ▲'
222git_symbol_unpulled=' ▼'
223git_symbol_unpushedunpulled=' ◆'
224git_symbol_dirty=' ◔'
225git_symbol_dirty_unpushed=' ◔ △'
226git_symbol_dirty_unpulled=' ◔ ▽'
227git_symbol_dirty_unpushedunpulled=' ◔ ◇'
228git_symbol_stash='🗎'
229git_update_period_minutes=15
230
231##==============================================================================
232## PYENV
233##==============================================================================
234font_color_pyenv="white"
235background_pyenv="blue"
236texteffect_pyenv="bold"
237
238##==============================================================================
239## KUBERNETES
240##==============================================================================
241font_color_kube="white"
242background_kube="purple"
243texteffect_kube="bold"
244
245##==============================================================================
246## TERRAFORM WORKSPACE
247##==============================================================================
248font_color_tf="purple"
249background_tf="light-purple"
250texteffect_tf="bold"
251
252##==============================================================================
253## CLOCK
254##==============================================================================
255font_color_clock="white"
256background_clock="black"
257texteffect_clock="bold"
258clock_format="%H:%M:%S"
259
260##==============================================================================
261## DATE
262##==============================================================================
263font_color_date="white"
264background_date="light-purple"
265texteffect_date="bold"
266date_format="${hari}, %d ${bulan} %Y"
267
268##==============================================================================
269## INPUT (user typed command)
270##==============================================================================
271font_color_input="45"
272background_input="none"
273texteffect_input="bold"
274
275##==============================================================================
276## EXTERNAL DEPENDENCIES
277##==============================================================================
278[ "$(type -t include)" != 'function' ] && {
279 include() {
280 {
281 [ -z "$_IR" ] &&
282 _IR="$PWD" &&
283 cd "$(dirname "${BASH_SOURCE[0]}")" &&
284 include "$1" &&
285 cd "$_IR" &&
286 unset _IR
287 } || {
288 local d="$PWD" &&
289 cd "$(dirname "$PWD/$1")" &&
290 . "$(basename "$1")" &&
291 cd "$d"
292 } || {
293 echo "Include failed $PWD->$1" &&
294 exit 1
295 }
296 }
297}
298
299synth_shell_prompt()
300{
301##==============================================================================
302## FUNCTIONS
303##==============================================================================
304
305getOSIcon()
306{
307 if [ -r /etc/os-release ]; then
308 local os_id
309 os_id=$(. /etc/os-release; printf '%s' "${ID:-}")
310
311 case "$os_id" in
312 debian)
313 printf '%s' "$SSP_ICON_DEBIAN"
314 ;;
315 *)
316 printf '%s' "$SSP_ICON_LINUX"
317 ;;
318 esac
319 else
320 printf '%s' "$SSP_ICON_LINUX"
321 fi
322}
323
324getGitBranch()
325{
326 if ! command -v git >/dev/null 2>&1; then
327 echo ""
328 return
329 fi
330
331 local branch
332 branch=$(git branch 2>/dev/null | sed -n '/^[^*]/d;s/*\s*\(.*\)/\1/p')
333 if [[ -z "$branch" ]]; then
334 echo ""
335 return
336 fi
337
338 if [ "$SSP_GIT_UPDATE_PERIOD_MINUTES" -ge 0 ]; then
339 local d="$PWD"
340 local max_lvls=25
341
342 while [ ! -e "./.git" ] && [ "$max_lvls" -gt 0 ]; do
343 cd ..
344 max_lvls=$((max_lvls - 1))
345 done
346
347 local dot_git="${PWD}/.git"
348 cd "$d" || return
349
350 if [ -f "$dot_git" ]; then
351 dot_git=$(grep 'gitdir' "$dot_git" | sed 's/gitdir:\ //g')
352 fi
353
354 local git_last_update=""
355 if [ -d "$dot_git" ] && [ -e "${dot_git}/FETCH_HEAD" ]; then
356 git_last_update=$(stat -c "%Y" "${dot_git}/FETCH_HEAD")
357 fi
358
359 if [ -n "$git_last_update" ]; then
360 local current_timestamp
361 local elapsed_minutes
362 current_timestamp=$(date +%s)
363 elapsed_minutes=$(((current_timestamp-git_last_update)/60))
364
365 if [ "$elapsed_minutes" -ge "$SSP_GIT_UPDATE_PERIOD_MINUTES" ]; then
366 git fetch --recurse-submodules >/dev/null 2>&1 &
367 fi
368 fi
369 fi
370
371 local is_dirty=false
372 [[ -n "$(git status --porcelain 2>/dev/null)" ]] && is_dirty=true
373
374 local is_ahead=false
375 [[ "$(git status --porcelain -u no -b 2>/dev/null)" == *"ahead"* ]] && is_ahead=true
376
377 local is_behind=false
378 [[ "$(git status --porcelain -u no -b 2>/dev/null)" == *"behind"* ]] && is_behind=true
379
380 local symbol
381 if $is_dirty && $is_ahead && $is_behind; then
382 symbol=$SSP_GIT_DIRTY_DIVERGED
383 elif $is_dirty && $is_ahead; then
384 symbol=$SSP_GIT_DIRTY_AHEAD
385 elif $is_dirty && $is_behind; then
386 symbol=$SSP_GIT_DIRTY_BEHIND
387 elif $is_dirty; then
388 symbol=$SSP_GIT_DIRTY
389 elif $is_ahead && $is_behind; then
390 symbol=$SSP_GIT_DIVERGED
391 elif $is_ahead; then
392 symbol=$SSP_GIT_AHEAD
393 elif $is_behind; then
394 symbol=$SSP_GIT_BEHIND
395 else
396 symbol=$SSP_GIT_SYNCED
397 fi
398
399 local tag=""
400 if [[ -n "$(git tag --points-at HEAD 2>/dev/null)" ]]; then
401 tag=" $(git tag --points-at HEAD 2>/dev/null)"
402 fi
403
404 local git_stash=""
405 local stashed_elements
406 stashed_elements=$(git stash list 2>/dev/null | wc -l)
407
408 if [ "$stashed_elements" -gt 0 ]; then
409 git_stash=" ${stashed_elements}${SSP_GIT_STASH}"
410 fi
411
412 echo "${branch}${symbol}${git_stash}${tag}"
413}
414
415getTerraform()
416{
417 if ! command -v terraform >/dev/null 2>&1; then
418 echo ""
419 return
420 fi
421
422 if [ ! -d .terraform ]; then
423 echo ""
424 return
425 fi
426
427 local tf
428 tf="$(terraform workspace show 2>/dev/null | tr -d '\n')"
429 echo "$tf"
430}
431
432getPyenv()
433{
434 if [ -n "${CONDA_DEFAULT_ENV:-}" ]; then
435 echo "$CONDA_DEFAULT_ENV"
436 elif [ -n "${VIRTUAL_ENV:-}" ]; then
437 local regex='PS1=\"\((.*?)\)\s\$\{PS1'
438 local pyenv
439 pyenv=$(perl -n -e"/$regex/ && print \$1" "$VIRTUAL_ENV/bin/activate" 2>/dev/null)
440
441 if [ -z "${pyenv}" ]; then
442 pyenv=$(basename "$VIRTUAL_ENV")
443 fi
444
445 echo "$pyenv"
446 fi
447}
448
449getKube()
450{
451 type kubectl &>/dev/null &&
452 type yq &>/dev/null &&
453 echo -n "$(kubectl config view | yq '.contexts[].context.cluster |select(.contexts[].name == .current-context)' | head -n 1)"
454}
455
456printSegment()
457{
458 local text=$1
459 local font_color=$2
460 local background_color=$3
461 local next_background_color=$4
462 local font_effect=$5
463
464 local no_color="\[$(getFormatCode -e reset)\]"
465 local text_format="\[$(getFormatCode -c "$font_color" -b "$background_color" -e "$font_effect")\]"
466 local separator_format="\[$(getFormatCode -c "$background_color" -b "$next_background_color")\]"
467
468 printf "${text_format}${segment_padding}${text}${segment_padding}${separator_padding_left}${separator_format}${separator_char}${separator_padding_right}${no_color}"
469}
470
471get_colors_for_element()
472{
473 case $1 in
474 "USER") echo "${SSP_COLORS_USER[@]}" ;;
475 "HOST") echo "${SSP_COLORS_HOST[@]}" ;;
476 "PWD") echo "${SSP_COLORS_PWD[@]}" ;;
477 "GIT") echo "${SSP_COLORS_GIT[@]}" ;;
478 "PYENV") echo "${SSP_COLORS_PYENV[@]}" ;;
479 "KUBE") echo "${SSP_COLORS_KUBE[@]}" ;;
480 "TF") echo "${SSP_COLORS_TF[@]}" ;;
481 "CLOCK") echo "${SSP_COLORS_CLOCK[@]}" ;;
482 "DATE") echo "${SSP_COLORS_DATE[@]}" ;;
483 "INPUT") echo "${SSP_COLORS_INPUT[@]}" ;;
484 esac
485}
486
487combine_elements()
488{
489 local first=$1
490 local second=$2
491 local colors_first=($(get_colors_for_element "$first"))
492 local colors_second=($(get_colors_for_element "$second"))
493
494 case $first in
495 "USER") local text="$user" ;;
496 "HOST") local text="$host" ;;
497 "PWD") local text="$path" ;;
498 "GIT") local text="$git_branch" ;;
499 "PYENV") local text="$pyenv" ;;
500 "KUBE") local text="$kube" ;;
501 "TF") local text="$tf" ;;
502 "CLOCK") local text="$clock" ;;
503 "DATE") local text="$date" ;;
504 "INPUT") local text="" ;;
505 *) local text="" ;;
506 esac
507
508 local text_color=${colors_first[0]}
509 local bg_color=${colors_first[1]}
510 local next_bg_color=${colors_second[1]}
511 local text_effect=${colors_first[2]}
512
513 printSegment "$text" "$text_color" "$bg_color" "$next_bg_color" "$text_effect"
514}
515
516prompt_command_hook()
517{
518 local elements=(${SSP_ELEMENTS[@]})
519
520 local user="${SSP_ICON_USER} ${USER}"
521 local host="$(getOSIcon) ${HOSTNAME}"
522 local path="${SSP_ICON_FOLDER} ${PWD}"
523
524 local git_branch
525 local pyenv
526 local kube
527 local tf
528 local clock
529 local date
530
531 git_branch="$(getGitBranch)"
532 pyenv="$(getPyenv)"
533 kube="$(getKube)"
534 tf="$(getTerraform)"
535 clock="${SSP_ICON_CLOCK} $(date +"${SSP_CLOCK_FORMAT}")"
536 date="${SSP_ICON_CALENDAR} $(date +"${SSP_DATE_FORMAT}")"
537
538 if [ -z "$git_branch" ]; then
539 elements=( ${elements[@]/"GIT"} )
540 fi
541 if [ -z "$pyenv" ]; then
542 elements=( ${elements[@]/"PYENV"} )
543 fi
544 if [ -z "$tf" ]; then
545 elements=( ${elements[@]/"TF"} )
546 fi
547 if [ -z "$kube" ]; then
548 elements=( ${elements[@]/"KUBE"} )
549 fi
550
551 case $TERM in
552 xterm*|rxvt*)
553 SSP_PWD="$PWD"
554 local titlebar="\[\033]0;\${USER}@\${HOSTNAME}: \${SSP_PWD}\007\]"
555 ;;
556 *)
557 local titlebar=""
558 ;;
559 esac
560
561 PS1="${titlebar}${SSP_VERTICAL_PADDING}${SSP_NEW_LINE_LINK_TOP}"
562
563 while [ "${#elements[@]}" -gt 1 ]; do
564 local current=${elements[0]}
565 local next=${elements[1]}
566 elements=("${elements[@]:1}")
567 PS1="$PS1$(combine_elements "$current" "$next")"
568 done
569
570 local input_colors=($(get_colors_for_element "${elements[0]}"))
571 local input_color=${input_colors[0]}
572 local input_bg=${input_colors[1]}
573 local input_effect=${input_colors[2]}
574 local input_format="\[$(getFormatCode -c "$input_color" -b "$input_bg" -e "$input_effect")\]"
575 local command_start_symbol="${input_format}${SSP_BASH_SYMBOL}"
576
577 PS1="${PS1}${SSP_PROMPT_COMM_SEP}${SSP_NEW_LINE_LINK_BOTTOM}${command_start_symbol} $input_format"
578}
579
580##==============================================================================
581## MAIN
582##==============================================================================
583 if $enable_vertical_padding; then
584 local vertical_padding="\n"
585 else
586 local vertical_padding=""
587 fi
588
589 if $enable_command_on_new_line; then
590 local new_line_link_top="╭"
591 local new_line_link_bottom="╰>"
592 local prompt_command_separation="\n"
593 local bash_symbol="\$"
594 else
595 local new_line_link_top=""
596 local new_line_link_bottom=""
597 local prompt_command_separation=""
598 local bash_symbol=""
599 fi
600
601 SSP_ELEMENTS=($format "INPUT")
602
603 SSP_COLORS_USER=($font_color_user $background_user $texteffect_user)
604 SSP_COLORS_HOST=($font_color_host $background_host $texteffect_host)
605 SSP_COLORS_PWD=($font_color_pwd $background_pwd $texteffect_pwd)
606 SSP_COLORS_GIT=($font_color_git $background_git $texteffect_git)
607 SSP_COLORS_PYENV=($font_color_pyenv $background_pyenv $texteffect_pyenv)
608 SSP_COLORS_KUBE=($font_color_kube $background_kube $texteffect_kube)
609 SSP_COLORS_TF=($font_color_tf $background_tf $texteffect_tf)
610 SSP_COLORS_CLOCK=($font_color_clock $background_clock $texteffect_clock)
611 SSP_COLORS_DATE=($font_color_date $background_date $texteffect_date)
612 SSP_COLORS_INPUT=($font_color_input $background_input $texteffect_input)
613
614 SSP_VERTICAL_PADDING=$vertical_padding
615 SSP_NEW_LINE_LINK_TOP=$new_line_link_top
616 SSP_NEW_LINE_LINK_BOTTOM=$new_line_link_bottom
617 SSP_PROMPT_COMM_SEP=$prompt_command_separation
618 SSP_BASH_SYMBOL=$bash_symbol
619 SSP_MAX_PWD_CHAR=${max_pwd_char:-25}
620
621 SSP_GIT_SYNCED=$git_symbol_synced
622 SSP_GIT_AHEAD=$git_symbol_unpushed
623 SSP_GIT_BEHIND=$git_symbol_unpulled
624 SSP_GIT_DIVERGED=$git_symbol_unpushedunpulled
625 SSP_GIT_DIRTY=$git_symbol_dirty
626 SSP_GIT_DIRTY_AHEAD=$git_symbol_dirty_unpushed
627 SSP_GIT_DIRTY_BEHIND=$git_symbol_dirty_unpulled
628 SSP_GIT_DIRTY_DIVERGED=$git_symbol_dirty_unpushedunpulled
629 SSP_GIT_STASH=$git_symbol_stash
630 SSP_GIT_UPDATE_PERIOD_MINUTES=$git_update_period_minutes
631
632 SSP_CLOCK_FORMAT=${clock_format:-"%H:%M:%S"}
633 SSP_DATE_FORMAT=${date_format:-"%A, %d %B %Y"}
634
635 SSP_ICON_USER=$icon_user
636 SSP_ICON_FOLDER=$icon_folder
637 SSP_ICON_CALENDAR=$icon_calendar
638 SSP_ICON_CLOCK=$icon_clock
639 SSP_ICON_DEBIAN=$icon_debian
640 SSP_ICON_LINUX=$icon_linux
641
642 none="$(tput sgr0)"
643 trap 'echo -ne "${none}"' DEBUG
644
645 PROMPT_COMMAND=prompt_command_hook
646}
647
648if [ "${BASH_SOURCE[0]}" == "${0}" ]; then
649 echo -e "Do not run this script, it will do nothing.\nPlease source it instead by running:\n"
650 echo -e "\t. ${BASH_SOURCE[0]}\n"
651 exit
652elif [[ $- == *i* ]]; then
653 if command -v tput >/dev/null 2>&1 && tput setaf 1 >&/dev/null; then
654 synth_shell_prompt
655 fi
656 unset synth_shell_prompt
657 unset include
658fi
659