From 36cd52f93d0d7c0dae4d64cc133b4ee35d90cf73 Mon Sep 17 00:00:00 2001 From: Denis Isidoro Date: Tue, 24 Sep 2019 16:13:57 -0300 Subject: [PATCH] Minor refactor (#57) - [x] Define OPTIONS as global in opts::eval - [x] Add script - [x] Minor changes --- cheats/kubernetes.cheat | 6 +-- navi | 10 +--- scripts/docker | 21 +++++++++ scripts/install | 8 ++-- src/arg.sh | 10 ++-- src/dict.sh | 9 +++- src/main.sh | 20 ++++++-- src/opts.sh | 7 +-- src/selection.sh | 2 +- src/str.sh | 6 +++ test/cheat_test.sh | 7 ++- test/core.sh | 16 +++---- test/log.sh | 102 ++++++++++++++++++++++++++++++++++++++++ 13 files changed, 187 insertions(+), 37 deletions(-) create mode 100755 scripts/docker create mode 100644 test/log.sh diff --git a/cheats/kubernetes.cheat b/cheats/kubernetes.cheat index c4584d2..ef14432 100644 --- a/cheats/kubernetes.cheat +++ b/cheats/kubernetes.cheat @@ -3,16 +3,16 @@ # Print resource documentation kubectl explain -# Get nodes (add option `-o wide` for details) +# Get nodes (add option '-o wide' for details) kubectl get nodes # Get namespaces kubectl get namespaces -# Get pods from namespace (add option `-o wide` for details) +# Get pods from namespace (add option '-o wide' for details) kubectl get pods -n -# Get pods from all namespace (add option `-o wide` for details) +# Get pods from all namespace (add option '-o wide' for details) kubectl get pods --all-namespaces # Get services from namespace diff --git a/navi b/navi index 4ff8219..fb89a6e 100755 --- a/navi +++ b/navi @@ -35,13 +35,7 @@ source "${SCRIPT_DIR}/src/main.sh" ##? full docs ##? Please refer to the README at https://github.com/denisidoro/navi -VERSION="0.9.1" +VERSION="0.9.2" -# workaround while dict::* can't handle newlines -case "${1:-}" in - help|--help) opts::extract_help "$0" && exit 0 ;; -esac - -OPTIONS="$(opts::eval "$@")" -export NAVI_PATH="$(dict::get "$OPTIONS" path)" +opts::eval "$@" main "$@" diff --git a/scripts/docker b/scripts/docker new file mode 100755 index 0000000..0088b86 --- /dev/null +++ b/scripts/docker @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +debian="${1:-false}" + +if $debian; then + docker run \ + -it \ + --entrypoint /bin/bash \ + -v "$(pwd):/navi" \ + debian \ + -c 'apt update; apt install -y git curl; git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf && yes | ~/.fzf/install; export PATH=$PATH:/navi; bash' +else + docker run \ + -it \ + --entrypoint /bin/bash \ + -v "$(pwd):/navi" \ + ellerbrock/alpine-bash-git \ + -c 'git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf && yes | ~/.fzf/install; export PATH=$PATH:/navi; bash' +fi + diff --git a/scripts/install b/scripts/install index 5219540..4aa75b6 100755 --- a/scripts/install +++ b/scripts/install @@ -8,6 +8,8 @@ script() { echo "${SCRIPT_DIR}/navi" '"$@"' } -BIN="/usr/local/bin/navi" -script > "$BIN" -chmod +x "$BIN" \ No newline at end of file +folder="${1:-/usr/local/bin}" +bin="${folder}/navi" + +script > "$bin" +chmod +x "$bin" \ No newline at end of file diff --git a/src/arg.sh b/src/arg.sh index d0eb04f..cf0bb38 100644 --- a/src/arg.sh +++ b/src/arg.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash ARG_REGEX="<[0-9a-zA-Z_]+>" -ARG_DELIMITER="£" +ARG_DELIMITER="\f" arg::dict() { - local -r fn="$(awk -F'---' '{print $1}')" - local -r opts="$(awk -F'---' '{print $2}')" + local -r input="$(cat)" + + local -r fn="$(echo "$input" | awk -F'---' '{print $1}')" + local -r opts="$(echo "$input" | awk -F'---' '{print $2}')" dict::new fn "$fn" opts "$opts" } @@ -34,7 +36,7 @@ arg::deserialize() { local -r out="$arg" fi - echo "$out" | sed "s/${ARG_DELIMITER}/ /g" + echo "$out" | tr "${ARG_DELIMITER}" " " } # TODO: separation of concerns diff --git a/src/dict.sh b/src/dict.sh index 4794425..3fc8ec5 100644 --- a/src/dict.sh +++ b/src/dict.sh @@ -1,5 +1,12 @@ #!/usr/bin/env bash +# for an explanation behind this namespace, please check +# https://medium.com/@den.isidoro/dictionaries-in-shell-scripts-61d34e1c91c6 + +# LIMITATIONS: +# values with non-trivial whitespaces (newlines, subsequent spaces, etc) +# aren't handled very well + DICT_DELIMITER='\f' dict::_post() { @@ -64,7 +71,7 @@ dict::get() { if [ $matches -gt 1 ]; then echo "$result" | dict::_unescape_value else - echo "$result" | sed -E "s/${prefix}//" | dict::_unescape_value + echo "$result" | sed -E "s/${prefix}//" | dict::_unescape_value | sed -E 's/^[[:space:]]*//' fi } diff --git a/src/main.sh b/src/main.sh index c392444..8f667fb 100644 --- a/src/main.sh +++ b/src/main.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +if ${NAVI_FORCE_GNU:-false}; then + source "${DOTFILES}/scripts/core/main.sh" +fi + source "${SCRIPT_DIR}/src/arg.sh" source "${SCRIPT_DIR}/src/cheat.sh" source "${SCRIPT_DIR}/src/dict.sh" @@ -56,8 +60,12 @@ handler::preview() { [ -n "$cheat" ] && selection::cmd "$selection" "$cheat" } -handler::text() { - dict::get "$OPTIONS" text +handler::help() { + echo "$TEXT" +} + +handler::version() { + echo "${VERSION:-unknown}" } main() { @@ -66,6 +74,7 @@ main() { local -r query="$(dict::get "$OPTIONS" query)" handler::preview "$query" \ || echo "Unable to find command for '$query'" + echo "$NAVI_PATH" ;; search) health::fzf @@ -73,8 +82,11 @@ main() { search::save "$query" || true handler::main ;; - text) - handler::text + version) + handler::version + ;; + help) + handler::help ;; *) health::fzf diff --git a/src/opts.sh b/src/opts.sh index 5280a0a..d66e188 100644 --- a/src/opts.sh +++ b/src/opts.sh @@ -25,8 +25,8 @@ opts::eval() { case $arg in --print) print=true ;; --no-interpolation) interpolation=false ;; - --version|version) dict::new entry_point "text" text "${VERSION:-unknown}" && exit 0 ;; - help|--help) dict::new entry_point "text" text "$(opts::extract_help "$0")" && exit 0 ;; + --version|version) entry_point="version" ;; + help|--help) entry_point="help"; TEXT="$(opts::extract_help "$0")" ;; --command-for) wait_for="command-for" ;; --no-preview) preview=false ;; --path) wait_for="path" ;; @@ -36,5 +36,6 @@ opts::eval() { esac done - dict::new entry_point "$entry_point" print "$print" interpolation "$interpolation" preview "$preview" query "${query:-}" path "$path" + OPTIONS="$(dict::new entry_point "$entry_point" print "$print" interpolation "$interpolation" preview "$preview" query "${query:-}")" + export NAVI_PATH="$path" } diff --git a/src/selection.sh b/src/selection.sh index 6745a47..b11bfb9 100644 --- a/src/selection.sh +++ b/src/selection.sh @@ -6,7 +6,7 @@ selection::dict() { local -r tags="$(echo "$str" | awk -F'[' '{print $NF}' | tr -d ']')" local -r core="$(echo "$str" | sed -e "s/ \[${tags}\]$//")" - dict::new core "$core" tags "$tags" + dict::new core "$core" tags "$tags" | sed "s/'''/'/g" } selection::core_is_comment() { diff --git a/src/str.sh b/src/str.sh index f58e962..65e67d2 100644 --- a/src/str.sh +++ b/src/str.sh @@ -29,3 +29,9 @@ str::last_paragraph_line() { str::first_word() { awk '{print $1}' } + +str::index_last_occurrence() { + local -r char="$1" + + awk 'BEGIN{FS=""}{ for(i=1;i<=NF;i++){ if($i=="'"$char"'"){ p=i } }}END{ print p }' +} diff --git a/test/cheat_test.sh b/test/cheat_test.sh index c88e8d1..93c69aa 100644 --- a/test/cheat_test.sh +++ b/test/cheat_test.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash -test::run "We can find at least one known cheatsheet" \ - 'cheat::find | grep -q "docker.cheat"' +assert_docker_cheat() { + cheat::find | grep -q "docker.cheat" +} + +test::run "We can find at least one known cheatsheet" assert_docker_cheat diff --git a/test/core.sh b/test/core.sh index e6718a3..8dd27c0 100644 --- a/test/core.sh +++ b/test/core.sh @@ -1,27 +1,27 @@ #!/usr/bin/env bash source "${SCRIPT_DIR}/src/main.sh" +source "${SCRIPT_DIR}/test/log.sh" -OPTIONS="$(opts::eval "$@")" -export NAVI_PATH="$(dict::get "$OPTIONS" path)" +opts::eval "$@" PASSED=0 FAILED=0 test::success() { PASSED=$((PASSED+1)) - echo "Test passed!" + log::success "Test passed!" } test::fail() { FAILED=$((FAILED+1)) - echo "Test failed..." + log::error "Test failed..." return } test::run() { echo - echo "-> $1" + log::note "$1" shift eval "$*" && test::success || test::fail } @@ -31,7 +31,7 @@ test::equals() { local -r expected="$(echo "${1:-}" | tr -d '\n' | sed 's/\\n//g')" if [[ "$actual" != "$expected" ]]; then - echo "Expected '${expected}' but got '${actual}'" + log::success "Expected '${expected}' but got '${actual}'" return 2 fi } @@ -39,10 +39,10 @@ test::equals() { test::finish() { echo if [ $FAILED -gt 0 ]; then - echo "${PASSED} tests passed but ${FAILED} failed... :(" + log::error "${PASSED} tests passed but ${FAILED} failed... :(" exit "${FAILED}" else - echo "All ${PASSED} tests passed! :)" + log::success "All ${PASSED} tests passed! :)" exit 0 fi } diff --git a/test/log.sh b/test/log.sh new file mode 100644 index 0000000..507c610 --- /dev/null +++ b/test/log.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +_export_colors() { + if ! ${DOT_COLORS_EXPORTED:-false}; then + if [ -z ${TERM:-} ] || [ $TERM = "dumb" ]; then + bold="" + underline="" + freset="" + purple="" + red="" + green="" + tan="" + blue="" + else + bold=$(tput bold) + underline=$(tput sgr 0 1) + freset=$(tput sgr0) + purple=$(tput setaf 171) + red=$(tput setaf 1) + green=$(tput setaf 76) + tan=$(tput setaf 3) + blue=$(tput setaf 38) + fi + + log_black=30 + log_red=31 + log_green=32 + log_yellow=33 + log_blue=34 + log_purple=35 + log_cyan=36 + log_white=37 + + log_regular=0 + log_bold=1 + log_underline=4 + + readonly DOT_COLORS_EXPORTED=true + fi +} + +log::color() { + _export_colors + local bg=false + case "$@" in + *reset*) echo "\e[0m"; exit 0 ;; + *black*) color=$log_black ;; + *red*) color=$log_red ;; + *green*) color=$log_green ;; + *yellow*) color=$log_yellow ;; + *blue*) color=$log_blue ;; + *purple*) color=$log_purple ;; + *cyan*) color=$log_cyan ;; + *white*) color=$log_white ;; + esac + case "$@" in + *regular*) mod=$log_regular ;; + *bold*) mod=$log_bold ;; + *underline*) mod=$log_underline ;; + esac + case "$@" in + *background*) bg=true ;; + *bg*) bg=true ;; + esac + + if $bg; then + echo "\e[${color}m" + else + echo "\e[${mod:-$log_regular};${color}m" + fi +} + +if [ -z ${LOG_FILE+x} ]; then + readonly LOG_FILE="/tmp/$(basename "$0").log" +fi + +_log() { + local template=$1 + shift + if ${log_to_file:-false}; then + echo -e $(printf "$template" "$@") | tee -a "$LOG_FILE" >&2 + else + echo -e $(printf "$template" "$@") + fi +} + +_header() { + local TOTAL_CHARS=60 + local total=$TOTAL_CHARS-2 + local size=${#1} + local left=$((($total - $size) / 2)) + local right=$(($total - $size - $left)) + printf "%${left}s" '' | tr ' ' = + printf " $1 " + printf "%${right}s" '' | tr ' ' = +} + +log::header() { _export_colors && _log "\n${bold}${purple}$(_header "$1")${freset}\n"; } +log::success() { _export_colors && _log "${green}✔ %s${freset}\n" "$@"; } +log::error() { _export_colors && _log "${red}✖ %s${freset}\n" "$@"; } +log::warning() { _export_colors && _log "${tan}➜ %s${freset}\n" "$@"; } +log::note() { _export_colors && _log "${blue}➜ %s${freset}\n" "$@"; }