Denis Isidoro 2019-10-28 16:56:19 -03:00 committed by GitHub
parent 70bf907840
commit a0c5a6293b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 252 additions and 83 deletions

View file

@ -88,7 +88,7 @@ as a [shell widget](#shell-widget) with no additional setup.
> to be able to run the command interactively, you will need to do one of the following: > to be able to run the command interactively, you will need to do one of the following:
- Install it to /usr/bin/local (via `sudo make install`) - Install it to /usr/bin/local (via `sudo make install`)
- Manually set your `PATH` so that navi can be found. - Manually set `$PATH` so that navi can be found.
You can manually update your path by adding a line like this in your `.zshrc`: You can manually update your path by adding a line like this in your `.zshrc`:
@ -250,6 +250,12 @@ List customization
Lists can be stylized with the [$FZF_DEFAULT_OPTS](https://github.com/junegunn/fzf) environment variable. This way, you can change the [color scheme](https://github.com/junegunn/fzf/wiki/Color-schemes), for example. Lists can be stylized with the [$FZF_DEFAULT_OPTS](https://github.com/junegunn/fzf) environment variable. This way, you can change the [color scheme](https://github.com/junegunn/fzf/wiki/Color-schemes), for example.
In addition:
- the `--fzf-overrides` option allows you to hide columns, for example
- the `--col-widths` option allows you to limit column widths
Please refer to `navi --help` for more details.
Related projects Related projects
---------------- ----------------

View file

@ -1,4 +1,4 @@
% tar, zip, gzip, compression % compression
# Create a tar containing files # Create a tar containing files
tar cf <name>.tar <files> tar cf <name>.tar <files>

View file

@ -1,4 +1,4 @@
% crontab, scheduling % crontab, schedule
# List cron jobs # List cron jobs
crontab -l crontab -l

View file

@ -1,4 +1,4 @@
% docker, container % docker
# Remove an image # Remove an image
docker image rm <image_id> docker image rm <image_id>
@ -47,7 +47,7 @@ $ container_id: docker ps --- --headers 1 --column 1
% docker-compose, container % docker-compose
# Builds, (re)creates, starts, and attaches to containers for all services # Builds, (re)creates, starts, and attaches to containers for all services
docker-compose up docker-compose up

52
docstring.txt Normal file
View file

@ -0,0 +1,52 @@
An interactive cheatsheet tool for the command-line
Usage:
navi [command] [<args>...] [options]
Commands:
search <cmd> Search for cheatsheets on online repositories
query <cmd> Pre-filter results
best <cmd> <args>... Considers the best match
widget <shell> Prints the path for the widget file to be sourced
Options:
--print Prevent script execution
--path List of :-separated paths to look for cheats
--no-interpolation Prevent argument interpolation
--no-preview Hide command preview window
--fzf-overrides Overrides for fzf commands [default: --with-nth 3,1,2 --exact]
--col-widths Set column widths [default: 15,50,0]
Environment variables:
NAVI_PATH List of :-separated paths to look for cheats
FZF_DEFAULT_OPTS Default fzf options (e.g. '--layout=reverse')
Examples:
navi # default behavior
navi --print # doesn't execute the snippet
navi --path '/some/dir:/other/dir' # uses custom cheats
navi search docker # uses online data
navi query git # filter results by "git"
navi best 'sql create db' root mydb # uses a snippet as a CLI
source "$(navi widget zsh)" # loads the zsh widget
navi --col-widths 10,50,0 # limits the first two columns's width
navi --fzf-overrides '--with-nth 1,2' # shows only the comment and tag columns
navi --fzf-overrides '--nth 1,2' # search will consider only the first two columns
navi --fzf-overrides '--no-exact' # looser search algorithm
More info:
search
http://cheat.sh is used for cheatsheet retrieval
please note that these cheatsheets may not work out of the box
always check the preview window before executing commands!
--col-widths
the number is the percentage relative to the terminal width
0 means that a column will be as wide as necessary
fzf's --with-nth
1 refers to the comment column; 2, snippet; 3, tag
full docs
please refer to the README at https://github.com/denisidoro/navi

33
navi
View file

@ -2,41 +2,8 @@
set -euo pipefail set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")" && pwd)" export NAVI_HOME="$(cd "$(dirname "$0")" && pwd)"
source "${NAVI_HOME}/src/main.sh" source "${NAVI_HOME}/src/main.sh"
##? An interactive cheatsheet tool for the command-line
##?
##? Usage:
##? navi [command] [<args>...] [options]
##?
##? Commands:
##? search <cmd> Search for cheatsheets on online repositories
##? query <cmd> Pre-filter results
##? best <cmd> <args>... Considers the best match
##?
##? Options:
##? --print Prevent script execution
##? --path List of paths to look for cheats
##? --no-interpolation Prevent argument interpolation
##? --no-preview Hide command preview window
##?
##? Examples:
##? navi
##? navi --path '/some/folder:/another/folder'
##? navi search awk
##? navi search docker --print
##? navi query git
##? navi best 'sql create db' root mydb
##?
##? More info:
##? search
##? Queries cheatsheets from http://cheat.sh
##? Please note that these cheatsheets may not work out of the box
##? Always check the preview window before executing commands!
##? full docs
##? Please refer to the README at https://github.com/denisidoro/navi
VERSION="0.14.3" VERSION="0.14.3"
NAVI_ENV="${NAVI_ENV:-prod}" NAVI_ENV="${NAVI_ENV:-prod}"

View file

@ -38,7 +38,7 @@ arg::next() {
} }
arg::deserialize() { arg::deserialize() {
local arg="$1" local arg="${1:-}"
arg="${arg:1:${#arg}-2}" arg="${arg:1:${#arg}-2}"
echo "$arg" \ echo "$arg" \

View file

@ -41,15 +41,68 @@ cheat::memoized_read_all() {
| cheat::join_lines | cheat::join_lines
} }
# TODO: move this elsewhere
cheat::get_index() {
local -r txt="$1"
local -r ref="$2"
local -r i="$(echo "$txt" | grep "${ref}\$" | awk '{print $1}')"
echo $((i - 1))
}
# TODO: move this elsewhere
cheat::with_nth() {
grep -Eo 'with\-nth +([^ ]+)' | awk '{print $NF}'
}
cheat::prettify() { cheat::prettify() {
awk 'function color(c,s) { local -r print="$(dict::get "$OPTIONS" print)"
printf("\033[%dm%s\033[0m",30+c,s) local -r widths="$(dict::get "$OPTIONS" col-widths | tr ',' $'\n')"
local -r numbered_with_nth="$(dict::get "$OPTIONS" fzf-overrides | cheat::with_nth | tr ',' $'\n' | str::with_line_numbers)"
if [ -n "$numbered_with_nth" ]; then
local -r comment_index="$(cheat::get_index "$numbered_with_nth" 1 2>/dev/null)"
local -r snippet_index="$(cheat::get_index "$numbered_with_nth" 2 2>/dev/null)"
local -r tag_index="$(cheat::get_index "$numbered_with_nth" 3 2>/dev/null)"
local -r comment_width="$(echo "$widths" | coll::get $comment_index 2>/dev/null || echo 0)"
local -r snippet_width="$(echo "$widths" | coll::get $snippet_index 2>/dev/null || echo 0)"
local -r tag_width="$(echo "$widths" | coll::get $tag_index 2>/dev/null || echo 0)"
local -r columns="$(ui::width)"
else
local -r comment_width=0
local -r snippet_width=0
local -r tag_width=0
local -r columns=0
fi
awk \
-v COMMENT_MAX=$((columns * comment_width / 100)) \
-v SNIPPET_MAX=$((columns * snippet_width / 100)) \
-v TAG_MAX=$((columns * tag_width / 100)) \
-v SEP="$ESCAPE_CHAR_3" \
'function color(c,s,max) {
if (max > 0 && length(s) > max) {
s=substr(s, 0, max)
s=s"…"
}
printf("\033[%dm%s", c, s)
} }
/^%/ { tags=" ["substr($0, 3)"]"; next } /^%/ { tags=substr($0, 3); next }
/^#/ { print color(4, $0) color(60, tags); next } /^#/ { comment=substr($0, 3); next }
/^\$/ { next } /^\$/ { next }
NF { print color(7, $0) color(60, tags); next }' BEGIN { ORS="" }
NF {
print color(34, comment, COMMENT_MAX)
print color(0, SEP, 0)
print color(37, $0, SNIPPET_MAX)
print color(0, SEP, 0)
print color(90, tags, TAG_MAX);
print color(0, SEP, 0)
print color(90, "\033", 0);
print "\n"
next
}'
} }
cheat::until_percentage() { cheat::until_percentage() {
@ -60,13 +113,20 @@ cheat::until_percentage() {
{ print $0 }' { print $0 }'
} }
cheat::from_tags() {
local -r cheats="$1"
local -r tags="$2"
echo "$cheats" \
| grep "% ${tags}" -A99999 \
| cheat::until_percentage
}
cheat::from_selection() { cheat::from_selection() {
local -r cheats="$1" local -r cheats="$1"
local -r selection="$2" local -r selection="$2"
local -r tags="$(dict::get "$selection" tags)" local -r tags="$(dict::get "$selection" tags)"
echo "$cheats" \ cheat::from_tags "$cheats" "$tags"
| grep "% ${tags}" -A99999 \
| cheat::until_percentage
} }

View file

@ -26,7 +26,7 @@ handler::main() {
local -r interpolation="$(dict::get "$OPTIONS" interpolation)" local -r interpolation="$(dict::get "$OPTIONS" interpolation)"
local cmd="$(selection::cmd "$selection" "$cheat")" local cmd="$(selection::snippet "$selection")"
local result arg value local result arg value
local i=0 local i=0
@ -50,10 +50,10 @@ handler::main() {
handler::preview() { handler::preview() {
local -r query="$1" local -r query="$1"
local -r selection="$(echo "$query" | selection::dict)"
local -r cheats="$(cheat::memoized_read_all)" local -r cheats="$(cheat::memoized_read_all)"
local -r selection="$(echo "$query" | selection::dict "$cheats")"
local -r cheat="$(cheat::from_selection "$cheats" "$selection")" local -r cheat="$(cheat::from_selection "$cheats" "$selection")"
[ -n "$cheat" ] && selection::cmd_or_comment "$selection" "$cheat" | cmd::unescape [ -n "$cheat" ] && ui::print_preview "$selection"
} }
handler::help() { handler::help() {

View file

@ -2,8 +2,8 @@
set -euo pipefail set -euo pipefail
opts::extract_help() { opts::extract_help() {
local -r file="$1" local -r file="${NAVI_HOME}/docstring.txt"
grep "^##?" "$file" | cut -c 5- cat "$file"
} }
opts::eval() { opts::eval() {
@ -17,6 +17,8 @@ opts::eval() {
local best=false local best=false
local query="" local query=""
local values="" local values=""
local col_widths="15,50,0"
local fzf_overrides="--with-nth 3,1,2 --exact"
case "${1:-}" in case "${1:-}" in
--version|version) entry_point="version"; shift ;; --version|version) entry_point="version"; shift ;;
@ -39,14 +41,21 @@ opts::eval() {
search) query="$arg"; wait_for=""; path="${path}:$(search::full_path "$query")"; continue ;; search) query="$arg"; wait_for=""; path="${path}:$(search::full_path "$query")"; continue ;;
query|best) query="$arg"; wait_for=""; continue ;; query|best) query="$arg"; wait_for=""; continue ;;
widget) SH="$arg"; wait_for=""; continue ;; widget) SH="$arg"; wait_for=""; continue ;;
col-widths) col_widths="$(echo "$arg" | xargs | tr ' ' ',')"; wait_for=""; continue ;;
fzf-overrides) fzf_overrides="$arg" ; wait_for=""; continue ;;
esac esac
case $arg in case $arg in
--print) print=true ;; --print) print=true ;;
--no-interpolation) interpolation=false ;; --no-interpolation) interpolation=false ;;
--interpolation) interpolation=true ;;
--no-preview) preview=false ;; --no-preview) preview=false ;;
--preview) preview=true ;;
--path|--dir) wait_for="path" ;; --path|--dir) wait_for="path" ;;
--no-autoselect) autoselect=false ;; --no-autoselect) autoselect=false ;;
--autoselect) autoselect=true ;;
--col-widths) wait_for="col-widths" ;;
--fzf-overrides) wait_for="fzf-overrides" ;;
*) values="$(echo "$values" | coll::add "$arg")" ;; *) values="$(echo "$values" | coll::add "$arg")" ;;
esac esac
done done
@ -59,7 +68,9 @@ opts::eval() {
autoselect "$autoselect" \ autoselect "$autoselect" \
query "$query" \ query "$query" \
best "$best" \ best "$best" \
values "$values")" values "$values" \
fzf-overrides "$fzf_overrides" \
col-widths "$col_widths")"
export NAVI_PATH="$path" export NAVI_PATH="$path"
} }

View file

@ -1,41 +1,69 @@
#!/usr/bin/env bash #!/usr/bin/env bash
selection::dict() { SELECTION_ESCAPE_STR=" "
local -r str="$(cat)"
local -r tags="$(echo "$str" | awk -F'[' '{print $NF}' | tr -d ']')" selection_str::cleanup() {
local -r core="$(echo "$str" | sed -e "s/ \[${tags}\]$//")" sed -E "s/ +/${SELECTION_ESCAPE_STR}/g"
dict::new core "$core" tags "$tags" | sed "s/'''/'/g"
} }
selection::core_is_comment() { selection_str::without_ellipsis() {
grep -qE '^#' tr -d "…"
} }
selection::cmd_or_comment() { selection_str::comment() {
local -r selection="$1" echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $1}' | selection_str::without_ellipsis
local -r cheat="$2" }
local -r always_cmd="${3:-false}"
local -r core="$(echo "$selection" | dict::get core)" selection_str::snippet() {
echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $2}' | selection_str::without_ellipsis
}
if echo "$core" | selection::core_is_comment; then selection_str::tags() {
echo "$cheat" \ echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $3}' | selection_str::without_ellipsis
| grep "$core" -A999 \ }
| str::last_paragraph_line \
| cmd::escape selection::resolve_ellipsis() {
elif $always_cmd; then local -r str="$(selection_str::cleanup)"
echo "$core" local -r cheats="$*"
if echo "$str" | grep -q "…"; then
local -r comment="$(selection_str::comment "$str")"
local -r snippet="$(selection_str::snippet "$str")"
local -r tags="$(selection_str::tags "$str")"
local -r cheat="$(cheat::from_tags "$cheats" "$tags")"
local -r tags2="$(echo "$cheat" | head -n1 | str::sub 2)"
local -r comment2="$(echo "$cheat" | grep "$comment" | str::sub 2)"
local -r snippet2="$(echo "$cheat" | grep "$comment2" -A 999| str::last_paragraph_line)"
echo "${comment2}${SELECTION_ESCAPE_STR}${snippet2}${SELECTION_ESCAPE_STR}${tags2}"
else else
echo "$cheat" \ echo "$str"
| grep "^${core}$" -B999 \
| str::reverse_lines \
| str::last_paragraph_line \
| cmd::escape
fi fi
} }
selection::cmd() { selection::dict() {
selection::cmd_or_comment "$@" true local -r cheats="$1"
local -r str="$(selection::resolve_ellipsis "$cheats")"
local -r comment="$(selection_str::comment "$str")"
local -r snippet="$(selection_str::snippet "$str")"
local -r tags="$(selection_str::tags "$str")"
dict::new comment "$comment" snippet "$snippet" tags "$tags" | sed "s/'''/'/g"
} }
selection::comment() {
local -r selection="$1"
dict::get "$selection" comment
}
selection::snippet() {
local -r selection="$1"
dict::get "$selection" snippet
}
selection::tags() {
local -r selection="$1"
dict::get "$selection" tags
}

View file

@ -62,4 +62,19 @@ str::not_empty() {
str::remove_empty_lines() { str::remove_empty_lines() {
sed '/^$/d' sed '/^$/d'
}
str::as_column() {
local -r txt="$(cat)"
local -r separator="$1"
if command_exists column; then
echo "$txt" | column -t -s "$separator"
else
echo "$txt" | awk -F "$separator" -vOFS='\t' 'NF > 0 { $1 = $1 } 1'
fi
}
str::with_line_numbers() {
awk '{printf("%d %s\n", NR,$0)}'
} }

View file

@ -2,6 +2,9 @@
ui::fzf() { ui::fzf() {
local -r autoselect="$(dict::get "$OPTIONS" autoselect)" local -r autoselect="$(dict::get "$OPTIONS" autoselect)"
local -r with_nth="$(dict::get "$OPTIONS" with-nth)"
local -r nth="$(dict::get "$OPTIONS" nth)"
local -r fzf_overrides="$(dict::get "$OPTIONS" fzf-overrides)"
local args local args
args+=("--height") args+=("--height")
@ -10,6 +13,9 @@ ui::fzf() {
args+=("--select-1") args+=("--select-1")
fi fi
local fzf_opts="${FZF_DEFAULT_OPTS:---height 70% --reverse --border --inline-info --cycle}"
export FZF_DEFAULT_OPTS="${FZF_DEFAULT_OPTS} ${fzf_overrides}"
local -r fzf_cmd="$([ $NAVI_ENV == "test" ] && echo "fzf_mock" || echo "fzf")" local -r fzf_cmd="$([ $NAVI_ENV == "test" ] && echo "fzf_mock" || echo "fzf")"
"$fzf_cmd" ${args[@]:-} --inline-info "$@" "$fzf_cmd" ${args[@]:-} --inline-info "$@"
} }
@ -18,7 +24,7 @@ ui::select() {
local -r cheats="$1" local -r cheats="$1"
local -r script_path="${NAVI_HOME}/navi" local -r script_path="${NAVI_HOME}/navi"
local -r preview_cmd="echo \'{}\' | $(arg::serialize_code) | xargs -I% \"${script_path}\" preview %" local -r preview_cmd="\"${script_path}\" preview \$(echo \'{}\' | $(arg::serialize_code))"
local -r query="$(dict::get "$OPTIONS" query)" local -r query="$(dict::get "$OPTIONS" query)"
local -r entry_point="$(dict::get "$OPTIONS" entry_point)" local -r entry_point="$(dict::get "$OPTIONS" entry_point)"
@ -30,7 +36,7 @@ ui::select() {
args+=("--ansi") args+=("--ansi")
if $preview; then if $preview; then
args+=("--preview"); args+=("$preview_cmd") args+=("--preview"); args+=("$preview_cmd")
args+=("--preview-window"); args+=("up:1") args+=("--preview-window"); args+=("up:2")
fi fi
if [[ -n "$query" ]] && $best; then if [[ -n "$query" ]] && $best; then
args+=("--filter"); args+=("${query} ") args+=("--filter"); args+=("${query} ")
@ -40,14 +46,38 @@ ui::select() {
if [ "$entry_point" = "search" ]; then if [ "$entry_point" = "search" ]; then
args+=("--header"); args+=("Displaying online results. Please refer to 'navi --help' for details") args+=("--header"); args+=("Displaying online results. Please refer to 'navi --help' for details")
fi fi
args+=("--delimiter"); args+=('\s\s+');
echo "$cheats" \ echo "$cheats" \
| cheat::prettify \ | cheat::prettify \
| str::as_column $(printf "$ESCAPE_CHAR_3") \
| ui::fzf "${args[@]}" \ | ui::fzf "${args[@]}" \
| ($best && head -n1 || cat) \ | ($best && head -n1 || cat) \
| selection::dict | selection::dict "$cheats"
} }
ui::clear_previous_line() { ui::clear_previous_line() {
tput cuu1 2>/dev/null && tput el || true tput cuu1 2>/dev/null && tput el || true
} }
ui::width() {
shopt -s checkwinsize; (:;:) 2> /dev/null || true
if command_exists tput; then
tput cols
else
echo 130
fi
}
ui::print_preview() {
local -r selection="$1"
local -r comment="$(selection::comment "$selection" | cmd::unescape)"
local -r snippet="$(selection::snippet "$selection" | cmd::unescape)"
local -r tags="$(selection::tags "$selection" | cmd::unescape)"
printf '\033[34m# '; echo -n "$comment"
printf " \033[90m["; echo -n "$tags"; echo "]"
printf '\033[0m'
echo "$snippet"
}