From bdf977e78aeaa00caf2f681381d4e4d43d84d57f Mon Sep 17 00:00:00 2001 From: pvincent Date: Mon, 31 Aug 2026 20:34:51 +0400 Subject: [PATCH] ansi colors --- bin/miaou-bash | 33 +---- {tools => bin}/pkg_add | 42 +++++-- etc/bash.bashrc | 2 + lib/ansi.bash | 155 ++++++------------------ lib/initiate.bash | 11 +- lib/uninstall.bash | 2 +- test/{stub => attic}/associative.bash | 0 test/{stub => attic}/tee_last_line.bash | 0 test/miaou-bash.test.bash | 70 ++++++++--- test/stub/ansi_error.bash | 80 ------------ tools/idem_apt_install | 2 + 11 files changed, 127 insertions(+), 270 deletions(-) rename {tools => bin}/pkg_add (52%) rename test/{stub => attic}/associative.bash (100%) rename test/{stub => attic}/tee_last_line.bash (100%) delete mode 100755 test/stub/ansi_error.bash diff --git a/bin/miaou-bash b/bin/miaou-bash index 82b6e40..f7fd818 100755 --- a/bin/miaou-bash +++ b/bin/miaou-bash @@ -7,38 +7,7 @@ if [[ ! -v MIAOU_BASH_LOADED_ONCE ]]; then readonly MIAOU_BASH_LOADED_ONCE=true readonly MIAOU_DEBUG=${MIAOU_DEBUG:-false} readonly MIAOU_ERROR_REGEX='^(.*): line ([0-9]+): miaou_error:' - - declare -A ANSI - ANSI[RESET]=0 - - ANSI[STYLE_ITALIC]=3 - ANSI[STYLE_BOLD]=1 - ANSI[STYLE_UNDERLINE]=4 - ANSI[STYLE_STRIKETHROUGH]=9 - - ANSI[FG_BLACK]=30 - ANSI[FG_RED]=31 - ANSI[FG_GREEN]=32 - ANSI[FG_YELLOW]=33 - ANSI[FG_BLUE]=34 - ANSI[FG_PURPLE]=35 - ANSI[FG_CYAN]=36 - ANSI[FG_WHITE]=37 - ANSI[FG_GRAY]=90 - ANSI[FG_LIGHTBLUE]=94 - ANSI[FG_MAGENTA]=95 - - ANSI[BG_BLACK]=40 - ANSI[BG_RED]=41 - ANSI[BG_GREEN]=42 - ANSI[BG_YELLOW]=43 - ANSI[BG_BLUE]=44 - ANSI[BG_PURPLE]=45 - ANSI[BG_CYAN]=46 - ANSI[BG_WHITE]=47 - ANSI[BG_GRAY]=90 - ANSI[BG_MAGENTA]=95 - readonly ANSI + source "$MIAOU_BASH_DIR"/lib/ansi.bash fi ## functions diff --git a/tools/pkg_add b/bin/pkg_add similarity index 52% rename from tools/pkg_add rename to bin/pkg_add index 8f4a7bf..2f4daa5 100755 --- a/tools/pkg_add +++ b/bin/pkg_add @@ -2,22 +2,30 @@ # CONSTANTS +REFRESH_SLOT='24 hours' +DEBIAN_REPO_DIR='/var/lib/apt/lists/partial' +ARCH_REPO_DIR='/var/lib/pacman/sync' + # functions function usage { echo "usage: $(basename "$0") " - echo 'idempotent package installation : update if necessary, install only if not yet done' + echo 'idempotent package installation : install only if not yet done + smart refresh repositories' +} + +# $1 -> file to compare with REFRESH_SLOT +function needs_refresh { + [[ $(date --date="-$REFRESH_SLOT" +%s) -gt $(date -d "$(stat -c %y "$1")" +%s) ]] } function debian_update_repo { - if [ "$(date --date='-12 hours' +%s)" -gt "$(date -d "$(stat -c %y /var/lib/apt/lists/partial)" +%s)" ]; then - echo "updating repositoring..." + if needs_refresh "$DEBIAN_REPO_DIR"; then + echo "updating repositories..." apt-get update fi } function debian_add { - debian_update_repo declare -a wanted=("$@") declare -a missing=() mapfile -t queries < <(dpkg-query -W -f='${Package}: ${Status}\n' "${wanted[@]}" 2>&1 || true) @@ -33,26 +41,38 @@ function debian_add { done if [[ "${#missing[@]}" -gt 0 ]]; then + debian_update_repo echo installing packages: "${missing[@]}" apt-get install -y "${missing[@]}" fi } +function arch_update_repo { + if needs_refresh "$ARCH_REPO_DIR"; then + echo "updating repositories..." + pacman -Syyu --noconfirm + fi +} + function arch_add { - pacman -Syyu --noconfirm - for i in "$@"; do + declare -a wanted=("$@") + declare -a missing=() + for i in "${wanted[@]}"; do if ! pacman -Ql "$i" &>/dev/null; then - pacman -S --noconfirm "$i" - elif [ -n "${VERBOSE+x}" ] && $VERBOSE; then - echo "pacman package <$i> already installed!" + missing+=("$i") fi done + if [[ "${#missing[@]}" -gt 0 ]]; then + arch_update_repo + echo installing packages: "${missing[@]}" + pacman -S --noconfirm "${missing[@]}" + fi } # MAIN -[ "$UID" -ne 0 ] && echo 'root privilege required' && exit 2 -[[ $# -lt 1 ]] && usage && builtin exit 1 +[ "$UID" -ne 0 ] && echo 'root privilege required' && builtin exit 1 +[[ $# -lt 1 ]] && usage && builtin exit 2 source "$MIAOU_BASH_DIR"/lib/functions.bash DISTRO_LIKE=$(get_distro_like) diff --git a/etc/bash.bashrc b/etc/bash.bashrc index 68a3631..ee63c8e 100644 --- a/etc/bash.bashrc +++ b/etc/bash.bashrc @@ -139,7 +139,9 @@ COLOR_OPTIONS=('--color=auto') alias ls="ls ${COLOR_OPTIONS[*]}" alias ll="ls ${COLOR_OPTIONS[*]} -l" alias la="ls ${COLOR_OPTIONS[*]} -la" +alias lt="ls ${COLOR_OPTIONS[*]} -lt" alias ltr="ls ${COLOR_OPTIONS[*]} -ltr" +alias l.="ls ${COLOR_OPTIONS[*]} -ld .*" alias grep="grep ${COLOR_OPTIONS[*]}" alias fgrep="fgrep ${COLOR_OPTIONS[*]}" alias egrep="egrep ${COLOR_OPTIONS[*]}" diff --git a/lib/ansi.bash b/lib/ansi.bash index 5e4af6d..2907a3c 100644 --- a/lib/ansi.bash +++ b/lib/ansi.bash @@ -1,120 +1,35 @@ -# CONSTANTS - -# MARGIN=0 -PADDING=0 -ANSI_RESET='\e[0m' - -BLOCK_CONTINUATION_COL= -BLOCK_CONTINUATION_SYMBOL=╦ - -declare -A STYLE_VARIANTS -STYLE_VARIANTS[BOLD]='\e[1m' -STYLE_VARIANTS[ITALIC]='\e[3m' -STYLE_VARIANTS[UNDERLINE]='\e[4m' -STYLE_VARIANTS[STRIKETHROUGH]='\e[9m' - -declare -A FG_COLORS -FG_COLORS[BLACK]='\e[30m' -FG_COLORS[RED]='\e[31m' -FG_COLORS[GREEN]='\e[32m' -FG_COLORS[YELLOW]='\e[33m' -FG_COLORS[BLUE]='\e[34m' -FG_COLORS[PURPLE]='\e[35m' -FG_COLORS[CYAN]='\e[36m' -FG_COLORS[WHITE]='\e[37m' -FG_COLORS[GRAY]='\e[90m' -FG_COLORS[MAGENTA]='\e[95m' - -declare -A BG_COLORS -BG_COLORS[BLACK]='\e[40m' -BG_COLORS[RED]='\e[41m' -BG_COLORS[GREEN]='\e[42m' -BG_COLORS[YELLOW]='\e[43m' -BG_COLORS[BLUE]='\e[44m' -BG_COLORS[PURPLE]='\e[45m' -BG_COLORS[CYAN]='\e[46m' -BG_COLORS[WHITE]='\e[47m' -# BG_COLORS["GRAY"]='\e[90m' -# BG_COLORS["MAGENTA"]='\e[95m' - -# FUNCTIONS - -function ansi_reset { - PADDING=0 - unset PADDING_LEFT PADDING_RIGHT BG FG -} - -function ansi_length { - local clean_text - clean_text=$(echo "$1" | sed 's/\x1B\[[0-9;]*m//g') - echo "${#clean_text}" -} - -function style_padding { - local left right - local padding_left=${PADDING_LEFT:-$PADDING} - local padding_right=${PADDING_RIGHT:-$PADDING} - - left=$(for _ in $(seq 1 "${padding_left}"); do builtin echo -n ' '; done) - style_ansi "$left" - - style_ansi "$1" - - right=$(for _ in $(seq 1 "${padding_right}"); do builtin echo -n ' '; done) - style_ansi "$right" -} - -function _style_fg { - [[ -n $1 ]] && echo "${FG_COLORS[$1]}" || true -} - -function _style_variant { - [[ -n $1 ]] && echo "${STYLE_VARIANTS[$1]}" || true -} - -function _style_bg { - if [[ -n "$1" ]]; then - [[ -v BG_COLORS[$1] ]] && echo "${BG_COLORS[$1]}" || >&2 echo "unknown BG value: $1" - fi -} - -function style_ansi { - local fg bg variant - fg=$(_style_fg "${FG:-}") && [[ -n $fg ]] && builtin echo -en "$fg" - bg=$(_style_bg "${BG:-}") && [[ -n $bg ]] && builtin echo -en "$bg" - variant=$(_style_variant "${STYLE:-}") && [[ -n $variant ]] && builtin echo -en "$variant" - builtin echo -en "$1" - [[ -n $fg || -n $bg || -n $variant ]] && builtin echo -en "$ANSI_RESET" - true -} - -function style_block { - local content length top bottom i - - content=$(style_padding "$1") - length=$(ansi_length "$content") - - top=$( - builtin echo -n "╔" - for _ in $(seq 1 "${length}"); do builtin echo -n '═'; done - builtin echo "╗" - ) - style_ansi "${top}" && builtin echo - - style_ansi "║" - style_ansi "${content}" - style_ansi "║" && builtin echo - - bottom=$( - builtin echo -n "╚" - for i in $(seq 1 "${length}"); do - if [[ $i == "$BLOCK_CONTINUATION_COL" ]]; then - builtin echo -n "$BLOCK_CONTINUATION_SYMBOL" - else - builtin echo -n '═' - fi - done - builtin echo "╝" - ) - style_ansi "${bottom}" && builtin echo -} +# TODO: build a global `declare -A __MIAOU_LOADED_LIBRARIES` + +if [[ ! -v ANSI ]]; then + declare -A ANSI + ANSI[RESET]=0 + + ANSI[STYLE_ITALIC]=3 + ANSI[STYLE_BOLD]=1 + ANSI[STYLE_UNDERLINE]=4 + ANSI[STYLE_STRIKETHROUGH]=9 + + ANSI[FG_BLACK]=30 + ANSI[FG_RED]=31 + ANSI[FG_GREEN]=32 + ANSI[FG_YELLOW]=33 + ANSI[FG_BLUE]=34 + ANSI[FG_PURPLE]=35 + ANSI[FG_CYAN]=36 + ANSI[FG_WHITE]=37 + ANSI[FG_GRAY]=90 + ANSI[FG_LIGHTBLUE]=94 + ANSI[FG_MAGENTA]=95 + + ANSI[BG_BLACK]=40 + ANSI[BG_RED]=41 + ANSI[BG_GREEN]=42 + ANSI[BG_YELLOW]=43 + ANSI[BG_BLUE]=44 + ANSI[BG_PURPLE]=45 + ANSI[BG_CYAN]=46 + ANSI[BG_WHITE]=47 + ANSI[BG_GRAY]=90 + ANSI[BG_MAGENTA]=95 + readonly ANSI +fi diff --git a/lib/initiate.bash b/lib/initiate.bash index 21b213e..a91e6cc 100644 --- a/lib/initiate.bash +++ b/lib/initiate.bash @@ -15,12 +15,8 @@ readonly BACKUP_SUFFIX='.ORIGINAL' # FUNCTIONS -function usr_bin_miaou_bash { - ln -sf "$MIAOU_BASH_DIR/bin/miaou-bash" /usr/bin/miaou-bash -} - function install_required_packages { - . "$MIAOU_BASH_DIR"/tools/pkg_add "${REQUIRED_PKGS[@]}" + . "$MIAOU_BASH_DIR"/bin/pkg_add "${REQUIRED_PKGS[@]}" } function fix_users_bashrc { @@ -198,8 +194,9 @@ function ghostty_global_config { done } -function link_miaou_bash { +function link_bin_scripts { sudo ln -sf "$MIAOU_BASH_DIR"/bin/miaou-bash /usr/bin/ + sudo ln -sf "$MIAOU_BASH_DIR"/bin/pkg_add /usr/bin/ } function create_sudoers { @@ -263,7 +260,7 @@ fix_terminfo_ghostty fix_locales ghostty_global_config link_config_files -link_miaou_bash +link_bin_scripts create_sudoers fetch_mode apply_mode diff --git a/lib/uninstall.bash b/lib/uninstall.bash index 624999f..e4ada88 100644 --- a/lib/uninstall.bash +++ b/lib/uninstall.bash @@ -22,10 +22,10 @@ function uninstall { sudo rm -f /etc/profile.d/20-miaou-bash.sh # deprecated: for older version sudo rm -f /etc/profile.d/zz-miaou-bash.sh sudo rm -f /etc/vimrc/vimrc.{azerty,core,local,miaou,rookie} - sudo rm -f /usr/bin/miaou-bash sudo rm -f /etc/sudoers.d/miaou-bash sudo rm -rf /var/lib/miaou-bash sudo rm -r /etc/bash_completion.d/miaou-bash.complete + sudo rm -f /usr/bin/{miaou-bash,pkg_add} sudo rm -rf /opt/miaou-bash cat <&2 echo "unknown ansi key: <$code>" + break + fi + done +} + +function print_passed { + ansi_code FG_GREEN + echo -en '.' + ansi_code RESET +} + +function print_bypassed { + ansi_code FG_YELLOW + echo -en 'b' + ansi_code RESET +} + +function print_failed { + ansi_code FG_RED + echo -en 'F' + ansi_code RESET +} + function assert_tty { ((test_count++)) if [[ -w /dev/tty ]]; then - echo -n . + print_passed return fi - echo -n F + print_failed failures+=("test -w /dev/tty") return 1 } @@ -26,11 +58,11 @@ function assert { cmd="$MIAOU_BASH_DIR/test/stub/scenario.bash $1" if $cmd >/dev/null 2>/dev/null 3>"$TMP_MIAOU"; then if [[ -s "$TMP_MIAOU" ]]; then - echo -n . + print_passed return fi fi - echo -n F + print_failed failures+=("$cmd") return 1 } @@ -48,11 +80,11 @@ function assert_grep { cmd="$MIAOU_BASH_DIR/test/stub/scenario.bash $script" if $cmd "${extra_args[@]}" >"$TMP_OUT" 2>/dev/null 3>/dev/null; then if grep -q "$content" "$TMP_OUT"; then - echo -n . + print_passed return fi fi - echo -n F + print_failed #TODO: quote extra_args for i in "${extra_args[@]}"; do safe_args+=$(printf '%q ' "$i"); done failures+=("$cmd $safe_args") @@ -78,7 +110,7 @@ function fail_type { if ! $locale_found; then ((test_bypassed++)) BYPASSED_LOCALES["$TEST_LANG"]=true - echo -n b + print_bypassed return fi fi @@ -95,10 +127,10 @@ function fail_type { fi if [[ $success == true ]]; then - echo -n . + print_passed return fi - echo -n F + print_failed failures+=("${lang_options[*]} $cmd") return 1 } @@ -109,11 +141,11 @@ function fail_stderr_grep { stderr_line="$2" if ! $cmd 2>"$TMP_ERR" && grep -q "$stderr_line" "$TMP_ERR"; then - echo -n . + print_passed return fi - echo -n F + print_failed failures+=("$cmd 2>&1 | grep -q \"$stderr_line\"") return 1 } @@ -126,12 +158,12 @@ function fail_stderr_count { if ! $cmd >/dev/null 2>"$TMP_ERR" 3>/dev/null; then mapfile -t stderr <"$TMP_ERR" if [[ "${#stderr[@]}" == "$count" ]]; then - echo -n . + print_passed return fi fi - echo -n F + print_failed failures+=("$cmd 2>$TMP_ERR") return 1 } @@ -144,12 +176,12 @@ function fail_miaou_count { if ! $cmd >/dev/null 2>/dev/null 3>"$TMP_MIAOU"; then mapfile -t miaou <"$TMP_MIAOU" if [[ "${#miaou[@]}" == "$count" ]]; then - echo -n . + print_passed return fi fi - echo -n F + print_failed failures+=("$cmd 3>$TMP_MIAOU") return 1 } @@ -159,7 +191,7 @@ function assert_locales_equally_populated { local locales_dir="$MIAOU_BASH_DIR"/locales local main_locale_file="$locales_dir"/"$1".sh if [[ ! -f $main_locale_file ]]; then - echo -n F + print_failed failures+=("missing locale file: $main_locale_file for locale: $1") return 1 fi @@ -169,7 +201,7 @@ function assert_locales_equally_populated { for extra in "$@"; do extra_locale_file="$locales_dir"/"$extra".sh if [[ ! -f $extra_locale_file ]]; then - echo -n F + print_failed failures+=("missing locale file: $extra_locale_file for locale: $extra") return 1 fi @@ -181,7 +213,7 @@ function assert_locales_equally_populated { if [[ ${#difference[@]} -gt 0 ]]; then variable_name=$(echo "${difference[1]}" | cut -d' ' -f2) change_type="${difference[0]}" - echo -n F + print_failed if [[ "$change_type" =~ d ]]; then failures+=("locale=$extra (in $extra_locale_file) misses variable (to add): ${variable_name}") else @@ -191,7 +223,7 @@ function assert_locales_equally_populated { fi done fi - echo -n . + print_passed } # main diff --git a/test/stub/ansi_error.bash b/test/stub/ansi_error.bash deleted file mode 100755 index 0671791..0000000 --- a/test/stub/ansi_error.bash +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env miaou-bash - -code=118 -failure_type='SUBSHELL TERM' -failure_payload='blabla' -stack_sources=('file.bash' './lib/utility.bash' 'file.bash') -stack_lines=(18 56 12) -stack_functions=('f2' 'f1' '
') - -# function ansi_block_continuation { -# tput sc -# tput cuu 1 -# BG=RED FG=BLACK style_ansi "\t╦" -# tput rc -# FG=RED style_ansi "\t║\n" -# } - -function ansi_block_error { - BG=RED - FG=BLACK - PADDING=2 - - local content detail exit_code - - content=$(style_ansi "$1") - content=$(style_padding "$content") - detail=$(BG=BLACK style_padding "$2") - exit_code=$(FG=CYAN style_ansi " $3") - BLOCK_CONTINUATION_COL=5 style_block "$content\t$detail$exit_code" - ansi_reset - FG=RED style_ansi " ║" - echo -} - -function ansi_traces { - local optional location sources lines functions - declare -n sources=$1 - declare -n lines=$2 - declare -n functions=$3 - optional=false - for i in "${!sources[@]}"; do - FG=RED style_ansi " ╟─⟶" - location=$(printf "%40s" "${sources[$i]}:${lines[$i]}") - is_true "$optional" && - location=$(FG=GRAY STYLE=ITALIC style_ansi "$location") || - location=$(STYLE=ITALIC style_ansi "$location") - builtin echo "${location} ${functions[$i]}" - optional=true - done -} - -function show_error { - >&2 printf '\r' - >&2 ansi_block_error "$failure_type" "$failure_payload" "$code" - >&2 ansi_traces stack_sources stack_lines stack_functions -} - -function echo_return { - echo TEXT && - return 11 - return 1 -} - -declare -F style_ansi >/dev/null || source "$MIAOU_BASH_DIR/lib/ansi.bash" - -echo START - -if echo_return; then - echo YOUPI -else - echo ERROR -fi - -if echo_return; then - echo YOUPI -else - echo ERROR -fi -# echo -n "standard flow:" && show_error -# echo -n "starting subshell: $(show_error)" diff --git a/tools/idem_apt_install b/tools/idem_apt_install index c2e211d..548a6cf 100755 --- a/tools/idem_apt_install +++ b/tools/idem_apt_install @@ -1,5 +1,7 @@ #!/usr/bin/env miaou-bash +# FIXME: deprecated, to be removed in a few months! + function usage { local BASECMD BASECMD=$(basename "$0")