diff options
author | EuAndreh <eu@euandre.org> | 2022-11-18 00:43:10 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2022-11-25 23:14:05 -0300 |
commit | 694c2dbc6fa71ee6582bade3ecc82e7b6ace83bf (patch) | |
tree | c946b6a8b60a1a150b01b19ef699e38e4e7e96b4 | |
parent | src/remembering.in: Use indented heredoc for usage() and help() (diff) | |
download | remembering-694c2dbc6fa71ee6582bade3ecc82e7b6ace83bf.tar.gz remembering-694c2dbc6fa71ee6582bade3ecc82e7b6ace83bf.tar.xz |
Refactor: rewrite logic to make it simpler and faster
Diffstat (limited to '')
-rwxr-xr-x | src/remembering.in | 163 | ||||
-rwxr-xr-x | tests/cli-opts.sh | 14 | ||||
-rwxr-xr-x | tests/ranking.sh | 109 | ||||
-rwxr-xr-x | tests/signals.sh | 13 |
4 files changed, 167 insertions, 132 deletions
diff --git a/src/remembering.in b/src/remembering.in index 2ce8e9c..907ffe1 100755 --- a/src/remembering.in +++ b/src/remembering.in @@ -14,12 +14,25 @@ help() { cat <<-'EOF' Options: - -p PROFILE profile to be used for gathering and storing data - -c COMMAND command to be run, reading from STDIN, writing to STDOUT - -h, --help show this help - -V, --version print the version number + -p PROFILE profile to be used for gathering and storing + data (default: create one based on $PWD) + -h, --help show this message + -V, --version print the version number - See "man remembering" for more information. + COMMAND command to be ran, reading from + STDIN, writing to STDOUT + + + Explanation FIXME. + + See "man @NAME@" for more information. + + + Examples: + + FIXME: + + $ FIXME EOF } @@ -27,10 +40,24 @@ version() { printf '%s %s %s\n' '@NAME@' '@VERSION@' '@DATE@' } -missing() { - printf 'Missing option: %s\n' "$1" + +uuid() { + od -xN20 /dev/urandom | + head -n1 | + awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' +} + +tmpname() { + echo "${TMPDIR:-/tmp}/@NAME@.tmpfile.$(uuid)" } +mkstemp() { + name="$(tmpname)" + touch "$name" + echo "$name" +} + + for flag in "$@"; do case "$flag" in --) @@ -50,15 +77,11 @@ for flag in "$@"; do esac done -COMMANDFLAG= -PROFILEFLAG= -while getopts 'hc:p:V' name; do - case "$name" in - c) - COMMANDFLAG="$OPTARG" - ;; +PROFILE_NAME="$(pwd | tr '/' '!')" +while getopts 'p:hV' flag; do + case "$flag" in p) - PROFILEFLAG="$OPTARG" + PROFILE_NAME="$OPTARG" ;; h) usage @@ -75,76 +98,68 @@ while getopts 'hc:p:V' name; do ;; esac done +shift $((OPTIND - 1)) -if [ -z "$COMMANDFLAG" ]; then - missing '-c COMMAND' >&2 - usage >&2 - exit 2 -fi -if [ -z "$PROFILEFLAG" ]; then - missing '-p PROFILE' >&2 +if [ $# = 0 ]; then + printf 'Missing "-- COMMAND"\n' >&2 usage >&2 exit 2 fi -COMMAND="$COMMANDFLAG" -PROFILE="${XDG_DATA_HOME:-$HOME/.local/share}/remembering/$PROFILEFLAG" +NAME='@NAME@' +PROFILE="${XDG_DATA_HOME:-$HOME/.local/share}"/$NAME/"$PROFILE_NAME" if [ ! -e "$PROFILE" ]; then mkdir -p "$(dirname "$PROFILE")" touch "$PROFILE" fi -MERGED="$(mktemp)" -FILTERED="$(mktemp)" -SORTED_STDIN="$(mktemp)" -cat - > "$SORTED_STDIN" - -# sed line to reverse lines, replacing GNU/Linux's tac -# Taken from: -# https://unix.stackexchange.com/questions/280685/reverse-sequence-of-a-file-with-posix-tools/280686#comment601716_280686 -xargs -I{} printf '0:%s\n' "{}" < "$SORTED_STDIN" | \ - sort -t: -k2,2 -m - "$PROFILE" | \ - sed '1!x;H;1h;$!d;g' | \ - sort -t: -k2,2 -u > "$MERGED" - -xargs -I{} printf 'filter_marker:%s\n' "{}" < "$SORTED_STDIN" | \ - cat - "$PROFILE" | \ - sort -t: -k2,2 | \ - awk '{ - split($0, l, ":") - rank = l[1] - entry = substr($0, length(rank) + 2) - if (rank != "filter_marker") { - prev_rank = rank - prev_entry = entry - } else { - if (prev_entry == entry) { - print prev_rank ":" entry - } else { - print "0:" entry - } +NEXT_PROFILE="$PROFILE".tmp +MERGED="$(mkstemp)" +FILTERED="$(mkstemp)" +trap 'rm -f "$NEXT_PROFILE" "$MERGED" "$FILTERED"' EXIT +CHOICE="$( + cat - | + sed 's/^/0 stdin /' | + sort -k3 -k1nr - "$PROFILE" | + tee "$MERGED" | + awk ' + { rest = substr($0, 3 + length($1) + length($2)) } + $2 == "profile" { seen[rest] += $1 } + $2 == "stdin" { printf "%s %s\n", seen[rest]+0, rest } + ' | + sort -k1nr | + cut -d' ' -f2- | + "$@" +)" + +if [ -z "$CHOICE" ]; then + exit +fi + +cat "$MERGED" | + cut -d' ' -f1,3- | + uniq -f1 | + awk -vCHOICE="$CHOICE" ' + BEGIN { inc = 1 } + + { rest = substr($0, 2 + length($1)) } + + rest == CHOICE { + printf "%s profile %s\n", $1 + inc, rest + found = 1 + next } - }' > "$FILTERED" - -CHOICE="$(sort -t: -k1nr,1 -k2,2 < "$FILTERED" | \ - cut -d: -f2- | \ - sh -c "$COMMAND")" - -if [ -n "$CHOICE" ]; then - NEW_PROFILE="$(mktemp)" - awk -v choice="$CHOICE" '{ - split($0, l, ":") - rank = l[1] - entry = substr($0, length(rank) + 2) - if (entry == choice) { - # Naively increment ranking by one - print rank + 1 ":" entry - } else { - print rank ":" entry + + { printf "%s profile %s\n", $1, rest } + + END { + if (!found) { + printf "%s profile %s\n", 0 + inc, CHOICE + } } - }' "$MERGED" > "$NEW_PROFILE" - mv "$NEW_PROFILE" "$PROFILE" - printf '%s\n' "$CHOICE" -fi + ' > "$NEXT_PROFILE" + +mv "$NEXT_PROFILE" "$PROFILE" +printf '%s\n' "$CHOICE" diff --git a/tests/cli-opts.sh b/tests/cli-opts.sh index ca2998a..ebaf18e 100755 --- a/tests/cli-opts.sh +++ b/tests/cli-opts.sh @@ -89,7 +89,7 @@ test_valid_options() { printf 'a\nb\nc\n' | \ ./src/remembering \ -p "always-unique-$(uuid)" \ - -c 'head -n1' \ + -- head -n1 \ 1>"$OUT" 2>"$ERR" STATUS=$? assert_status 0 @@ -176,13 +176,13 @@ test_environment_variables_and_precedence() { printf 'a\n' | \ XDG_DATA_HOME="$XDG" ./src/remembering \ -p "$PROFILE" \ - -c 'head -n1' \ + -- head -n1 \ 1>"$OUT" 2>"$ERR" STATUS=$? assert_status 0 assert_empty_stderr assert_stdout 'a' - assert_stream "\$XDG PROFILE" "$XDG/remembering/$PROFILE" '1:a' + assert_stream "\$XDG PROFILE" "$XDG/remembering/$PROFILE" '1 profile a' N="$LINENO" OUT="$(mktemp)" @@ -192,13 +192,13 @@ test_environment_variables_and_precedence() { printf 'b\n' | \ HOME="$HHOME" XDG_DATA_HOME='' ./src/remembering \ -p "$PROFILE" \ - -c 'head -n1' \ + -- head -n1 \ 1>"$OUT" 2>"$ERR" STATUS=$? assert_status 0 assert_empty_stderr assert_stdout 'b' - assert_stream "\$HHOME PROFILE" "$HHOME/.local/share/remembering/$PROFILE" '1:b' + assert_stream "\$HHOME PROFILE" "$HHOME/.local/share/remembering/$PROFILE" '1 profile b' N="$LINENO" OUT="$(mktemp)" @@ -209,14 +209,14 @@ test_environment_variables_and_precedence() { printf 'c\n' | \ HOME="$HHOME" XDG_DATA_HOME="$XDG" ./src/remembering \ -p "$PROFILE" \ - -c 'head -n1' \ + -- head -n1 \ 1>"$OUT" 2>"$ERR" STATUS=$? assert_status 0 assert_empty_stderr assert_stdout 'c' assert_stream \ - "\$XDG and \$HOME PROFILE" "$XDG/remembering/$PROFILE" '1:c' + "\$XDG and \$HOME PROFILE" "$XDG/remembering/$PROFILE" '1 profile c' if [ -e "$HHOME/.local/share/remembering/$PROFILE" ]; then printf "\nERR: The file in \$HHOME/.local should't exist\n" >&2 diff --git a/tests/ranking.sh b/tests/ranking.sh index 6649156..ea4365d 100755 --- a/tests/ranking.sh +++ b/tests/ranking.sh @@ -40,7 +40,7 @@ pick_x() { echo "${2:-$INPUT}" | \ ./src/remembering \ -p "$PROFILE" \ - -c "tee -a /dev/stderr | grep \"$PICK\"" \ + -- sh -c "tee -a /dev/stderr | grep -F \"$PICK\"" \ 1>"$OUT" 2>"$ERR" STATUS=$? assert_status 0 @@ -56,26 +56,28 @@ assert_first() { fi } -BASE_PROFILE='0:a -0:b -0:c -0:d -0:e' -BASE_PROFILE_A_PICKED='1:a -0:b -0:c -0:d -0:e' +BASE_PROFILE='0 profile a +0 profile b +0 profile c +0 profile d +0 profile e' +BASE_PROFILE_A_PICKED='1 profile a +0 profile b +0 profile c +0 profile d +0 profile e' assert_profile() { if [ "$(cat "$XDG_DATA_HOME/remembering/$1")" != "$2" ]; then printf '\n%s: Bad profile merge (%s).\n\nExpected:\n%s\nGot\n%s\n' \ "$(ERROR)" "$PROFILE" "$2" "$(cat "$XDG_DATA_HOME/remembering/$1")" >&2 + print_debug_info exit 1 fi } test_picking_first_makes_it_be_always_first() { testing 'picking first makes it be always first' + N="$LINENO" OUT="$(mktemp)" ERR="$(mktemp)" PROFILE="always-picks-first-$(uuid)" @@ -83,7 +85,7 @@ test_picking_first_makes_it_be_always_first() { printf 'always-picked\nnever-picked\n' | \ ./src/remembering \ -p "$PROFILE" \ - -c 'head -n1' \ + -- head -n1 \ 1>"$OUT" 2>"$ERR" STATUS=$? assert_status 0 @@ -95,6 +97,7 @@ test_picking_first_makes_it_be_always_first() { test_promoting_values() { testing 'promoting values' + N="$LINENO" PROFILE="promoting-$(uuid)" pick_x h @@ -109,6 +112,7 @@ test_promoting_values() { test_higher_values_loose_tie() { testing 'higher values loose tie' + N="$LINENO" PROFILE="higher-loose-tie-$(uuid)" pick_x f @@ -122,6 +126,7 @@ test_higher_values_loose_tie() { test_smaller_values_win_tie() { testing 'smaller values win tie' + N="$LINENO" PROFILE="smaller-win-tie-$(uuid)" pick_x d @@ -135,6 +140,7 @@ test_smaller_values_win_tie() { test_many_sequential_picks() { testing 'many sequential pick' + N="$LINENO" PROFILE="many-sequential-picks-$(uuid)" pick_x b @@ -181,14 +187,15 @@ s' test_pick_inexisting_value() { testing 'pick inexisting value' + N="$LINENO" OUT="$(mktemp)" ERR="$(mktemp)" PROFILE="pick-inexisting-value-$(uuid)" - echo '0:a -0:b -0:c -0:d -0:e' > "$XDG_DATA_HOME/remembering/$PROFILE" + echo '0 profile a +0 profile b +0 profile c +0 profile d +0 profile e' > "$XDG_DATA_HOME/remembering/$PROFILE" INPUT='a b c @@ -198,46 +205,48 @@ e' echo "$INPUT" | \ ./src/remembering \ -p "$PROFILE" \ - -c 'echo f' \ - 1>"$OUT" 2>"$ERR" + -- echo f \ + 1>"$OUT" # 2>"$ERR" STATUS=$? assert_status 0 assert_stdout f - assert_profile "$PROFILE" '1:f -0:a -0:b -0:c -0:d -0:e' - + assert_profile "$PROFILE" '0 profile a +0 profile b +0 profile c +0 profile d +0 profile e +1 profile f' test_ok } test_stdin_profile_merging() { testing 'STDIN/profile merging' + N="$LINENO" PROFILE="stdin-profile-merging-$(uuid)" - echo '0:a -0:b -0:c -0:z' > "$XDG_DATA_HOME/remembering/$PROFILE" + echo '0 profile a +0 profile b +0 profile c +0 profile z' > "$XDG_DATA_HOME/remembering/$PROFILE" INPUT='a b c d e' + EXPECTED='a +b +c +d +e' pick_x a "$INPUT" - if [ "$(cat "$ERR")" != "$INPUT" ]; then - printf '\nERR: Bad profile merge.\n\nExpected:\n%s\nGot:\n%s\n' \ - "$INPUT" "$(cat "$ERR")" >&2 - exit 1 - fi + assert_stderr "$EXPECTED" test_ok } test_stdin_is_larger_than_profile() { testing 'STDIN is larger than profile' + N="$LINENO" PROFILE="stdin-is-larger-than-profile-$(uuid)" - echo '0:a' > "$XDG_DATA_HOME/remembering/$PROFILE" + echo '0 profile a' > "$XDG_DATA_HOME/remembering/$PROFILE" INPUT='a b c @@ -250,6 +259,7 @@ e' test_stdin_is_smaller_than_profile() { testing 'STDIN is smaller than profile' + N="$LINENO" PROFILE="stdin-is-smaller-than-profile-$(uuid)" echo "$BASE_PROFILE" > "$XDG_DATA_HOME/remembering/$PROFILE" INPUT='a' @@ -260,6 +270,7 @@ test_stdin_is_smaller_than_profile() { test_stdin_is_empty() { testing 'STDIN is empty' + N="$LINENO" PROFILE="stdin-is-empty-$(uuid)" echo "$BASE_PROFILE" > "$XDG_DATA_HOME/remembering/$PROFILE" OUT="$(mktemp)" @@ -268,11 +279,12 @@ test_stdin_is_empty() { printf '' | \ ./src/remembering \ -p "$PROFILE" \ - -c 'tee -a /dev/stderr | head -n1' \ + -- sh -c 'tee -a /dev/stderr | head -n1' \ 1>"$OUT" 2>"$ERR" STATUS=$? assert_status 0 + assert_stderr '' assert_stdout '' assert_profile "$PROFILE" "$BASE_PROFILE" test_ok @@ -280,6 +292,7 @@ test_stdin_is_empty() { test_profile_does_not_exist() { testing 'profile does not exist' + N="$LINENO" PROFILE="profile-does-not-exist-$(uuid)" INPUT='a b @@ -293,6 +306,7 @@ e' test_profile_is_empty() { testing 'profile is empty' + N="$LINENO" PROFILE="profile-is-empty-$(uuid)" printf '' > "$XDG_DATA_HOME/remembering/$PROFILE" INPUT='a @@ -307,11 +321,12 @@ e' test_names_with_spaces() { testing 'names with spaces' + N="$LINENO" PROFILE="names-with-spaces-$(uuid)" INPUT='a b c d e f' - EXPECTED='1:a b c -0:d e f' + EXPECTED='1 profile a b c +0 profile d e f' pick_x 'a b c' "$INPUT" assert_profile "$PROFILE" "$EXPECTED" test_ok @@ -319,13 +334,14 @@ d e f' test_really_long_list() { testing 'really long list' + N="$LINENO" OUT="$(mktemp)" ERR="$(mktemp)" PROFILE="really-long-list-$(uuid)" seq 999999 | \ ./src/remembering \ -p "$PROFILE" \ - -c 'head -n1' \ + -- head -n1 \ 1>"$OUT" 2>"$ERR" STATUS=$? assert_status 0 @@ -336,15 +352,16 @@ test_really_long_list() { test_utf8_commands() { testing 'UTF-8 commands' + N="$LINENO" PROFILE="utf8-commands-$(uuid)" INPUT='❤️ á è ŭ 😀' - EXPECTED='0:á -0:è -1:ŭ 😀 -0:❤️' + EXPECTED='0 profile á +0 profile è +1 profile ŭ 😀 +0 profile ❤️' pick_x 'ŭ 😀' "$INPUT" assert_profile "$PROFILE" "$EXPECTED" test_ok @@ -355,7 +372,7 @@ test_promoting_values test_higher_values_loose_tie test_smaller_values_win_tie test_many_sequential_picks -# test_pick_inexisting_value +test_pick_inexisting_value test_stdin_profile_merging test_stdin_is_larger_than_profile test_stdin_is_smaller_than_profile @@ -363,5 +380,5 @@ test_stdin_is_empty test_profile_does_not_exist test_profile_is_empty test_names_with_spaces -# test_really_long_list +test_really_long_list test_utf8_commands diff --git a/tests/signals.sh b/tests/signals.sh index 28b2e89..ff684cc 100755 --- a/tests/signals.sh +++ b/tests/signals.sh @@ -3,19 +3,21 @@ set -u . tests/lib.sh -export XDG_DATA_HOME="$PWD/tests/test-profiles" +export XDG_DATA_HOME="$PWD/tests/test-profiles/signals-$(uuid)" test_status_is_zero_when_command_is_successful() { testing 'status is 0 when command is successful' - printf 'a\n' | ./src/remembering -pp1 -c 'head -n1' 1>/dev/null 2>/dev/null + N="$LINENO" + + printf 'a\n' | ./src/remembering -pp1 -- head -n1 1>/dev/null 2>/dev/null STATUS=$? assert_status 0 - printf '' | ./src/remembering -pp2 -c 'exit 0' 1>/dev/null 2>/dev/null + printf '' | ./src/remembering -pp2 -- true 1>/dev/null 2>/dev/null STATUS=$? assert_status 0 - seq 9 | ./src/remembering -pp3 -c 'grep 7' 1>/dev/null 2>/dev/null + seq 9 | ./src/remembering -pp3 -- grep 7 1>/dev/null 2>/dev/null STATUS=$? assert_status 0 @@ -24,8 +26,9 @@ test_status_is_zero_when_command_is_successful() { test_status_is_forwarded_from_command() { testing 'status is forwarded from command' + N="$LINENO" for status in $(seq 1 125); do - printf '' | ./src/remembering -pp4 -c "exit $status" 1>/dev/null 2>/dev/null + printf '' | ./src/remembering -pp4 -- sh -c "exit $status" 1>/dev/null 2>/dev/null STATUS=$? assert_status "$status" done |