Last active
November 16, 2020 04:28
-
-
Save magpie514/c3f64ff622fd1760a7f3170fc4fd44ce to your computer and use it in GitHub Desktop.
Implementation of blezz (https://github.com/Blezzing/blezz) in pure bash.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Bblezz: An implementation of blezz (https://github.com/Blezzing/blezz) in pure bash. | |
# Output of actions can be redirected like # { out="$( COLUMNS=$COLUMNS bblezz file.blezz 2>&1 1>&5 5>&-)"; } 5>&1; | |
# TODO List: | |
# [v] Handle trying to enter undefined directories. | |
# [ ] trim_surrounding_spaces > Remove spaces from the right properly too. | |
# [ ] general > command line options to define glyphs, colors and starting dir. | |
# Reset ANSI colors: | |
CLX='\033[0;0m' | |
# ANSI Colors: Foreground: ${CF[x]} Background: ${CB[x]} Clear: $CLX | |
# 00:Black 01:Red 02:Green 03:Yellow 04:Blue 05:Pink 06:Cyan 07:Gray | |
# 08:Black 09:Red 10:Green 11:Yellow 12:Blue 13:Pink 14:Cyan 15:Gray | |
CF=( '\033[3'{0..7}'m' '\033[9'{0..7}'m' ) | |
CB=( '\033[4'{0..7}'m' '\033[10'{0..7}'m' ) | |
EL='\033[K\n' #Move to end of line. | |
# Options ##################################################################### | |
# Glyphs | |
GLYPH_DIR="/" | |
GLYPH_CMD="~" | |
GLYPH_BCK="<" | |
# Colors | |
COLOR_FG="15" #White | |
COLOR_BG="4" #Blue | |
COLOR_MSG="7" #Red | |
############################################################################### | |
# The lack of nested arrays in Bash forces things to be a bit messy, but can be done. | |
declare -A dirs #This array stores the directories like [dir]:[data]. Data is a field of 4 values separated by tabs (key, label, type (dir/act/back) and command) | |
declare -A keys #This array stores the keys that the displayed directory accepts like [key]:[data]. data is (type,command) separated by tabs. | |
declare -a hist #History. A fairly rough stack where opened dirs are stored, so the "back" action works properly. | |
current_dir="" #Current dir. | |
msgline_pos="" #Message line position. | |
function _exit { #Clean up and restore screen buffer. | |
echo -e '\033[?47l' # restore screen | |
#Clean up globals to avoid polluting env. | |
unset dirs; unset keys; unset hist | |
unset current_dir; unset msgline_pos | |
unset CLX; unset CF; unset CB; unset EL | |
unset GLYPH_DIR; unset GLYPH_CMD; unset GLYPH_BCK | |
unset COLOR_BG; unset COLOR_FG | |
exit | |
} | |
trap _exit SIGINT SIGHUP SIGTERM EXIT | |
function get_cursorpos { | |
local pos; | |
read -rsdR -p $'\E[6n' pos && pos="${pos#*[}" | |
echo -n "$pos" | |
} | |
function print_hist { | |
local out="" | |
local n=${#hist[@]} | |
local sep="" | |
for (( i = n-1; i >= 0; i-- )); do | |
out="$out$sep${hist[i]}" | |
sep="/" | |
done | |
echo "$out" | |
} | |
function fancy_header { #Draw the header (dir) | |
local C="${CF[$COLOR_FG]}${CB[$COLOR_BG]}"; local I="${CF[$COLOR_BG]}${CB[$COLOR_FG]}" #Drawing color and inverted color. | |
local H; H="$(print_hist)" | |
#printf "$I%*s$C$EL" "$(( (${#1} + ${#H} + ${COLUMNS:-80}) / 2))" "$H/$1" #Center the dir name. | |
printf "$I%*s$EL" "$(( (${#1} + ${#H} + ${COLUMNS:-80}) / 2))" "$H/$1" #Center the dir name. | |
} | |
function fancy_line { #Draw the contents. | |
local C="${CF[$COLOR_FG]}${CB[$COLOR_BG]}"; local I="${CF[$COLOR_BG]}${CB[$COLOR_FG]}" | |
echo -ne "$I$1 [$2] $C $3 $EL$CLX" | |
} | |
function msgline_print { #Move cursor to last (message) line. | |
local C="${CF[$COLOR_MSG]}"; | |
echo -ne "\033[${msgline_pos}H" | |
echo -ne "$C $1 $EL$CLX" | |
} | |
function is_dir_declaration { #Check if current line defines a directory. | |
[[ $1 =~ :$ ]] && return 0 | |
return 1 | |
} | |
function is_act_ref { #Check if current line is an action. | |
[[ $1 =~ act(.*) ]] && return 0 | |
return 1 | |
} | |
function is_dir_ref { #Check if current line is a directory open action. | |
[[ $1 =~ dir(.*) ]] && return 0 | |
return 1 | |
} | |
function trim_surrounding_spaces { #Remove potential spaces around the elements. | |
local out="${1#${1%%[![:space:]]*}}" | |
#out="${out%${out##*[![:space]]}}" #TODO: Remove spaces from the right properly too. | |
echo "$out" | |
} | |
function parse_act { #Parse an action definition. | |
[[ $1 =~ act\((.),(.*),(.*)\) ]] && { #Get [key,label,command]. | |
local A;A="${BASH_REMATCH[1]}"; local B;B=$(trim_surrounding_spaces "${BASH_REMATCH[2]}"); local C;C=$(trim_surrounding_spaces "${BASH_REMATCH[3]}") | |
echo -ne "$A\t$B\tCMD\t$C" #Format is key, label, type (CMD), command] | |
} | |
} | |
function parse_dir { #Parse a directory open action. | |
[[ $1 =~ dir\((.),(.*)\) ]] && { #Get [key,label]. | |
local A; A="${BASH_REMATCH[1]}"; local B; B=$(trim_surrounding_spaces "${BASH_REMATCH[2]}") | |
echo -ne "$A\t$B\tDIR\t$B" #Format is [key, label, type (CMD), command], but label is == command. | |
} | |
} | |
function load_blezz { #Load blezz config. Should conform to spec for blezz, doesn't take rofi-blezz actReloads into account (yet?) | |
[[ -f $1 ]] && { | |
local current_dir="" | |
while read -r i; do | |
is_dir_declaration "$i" && { #Directory declaration. Once set, we switch to it, then load any following actions in the current one. | |
local dirname=${i%:} | |
current_dir=$dirname | |
dirs[${i%:}]="" #Empty list for this dir now. | |
} | |
is_act_ref "$i" && { #Action declaration. Added to current dir, if any is set. | |
[[ -n $current_dir ]] && { | |
IFS= | |
printf -v dirs["$current_dir"] "%s\n" "${dirs[$current_dir]}$(parse_act "$i")" | |
} | |
} | |
is_dir_ref "$i" && { #Change dir action declaration. Same as regular actions. | |
[[ -n $current_dir ]] && { | |
IFS= | |
printf -v dirs["$current_dir"] "%s\n" "${dirs[$current_dir]}$(parse_dir "$i")" | |
} | |
} | |
done < "$1" | |
} | |
} | |
function get_glyph { #Get the glyphs to be displayed before the key. | |
case "$1" in | |
CMD) echo $GLYPH_CMD;; | |
DIR) echo $GLYPH_DIR;; | |
BCK) echo $GLYPH_BCK;; | |
*) echo "?";; | |
esac | |
} | |
function clear_keys { #Clear content of keys array. Done this way because unsetting keys in a function and remaking it will make it non-global. | |
for i in "${!keys[@]}"; do | |
unset keys["$i"] | |
done | |
} | |
function show_dir { #Switch to, and display, all commands in this directory. | |
clear | |
local dir="${1:-Main}" | |
[[ -v dirs[$dir] ]] && { | |
current_dir="$dir" | |
fancy_header "$dir" | |
clear_keys | |
local dir_content="${dirs[$dir]}" #Load contents of this dir. | |
while read -r line; do | |
[[ -n $line ]] && { | |
IFS=$'\t' | |
read -ra A <<< "$line" | |
local key="${A[0]}"; local label="${A[1]}"; local type="${A[2]}"; local command="${A[3]}" | |
printf -v keys[$key] "%s\t%s" "$type" "$command" #Store key data so it can be accessed by using bash associative arrays with the input as key. | |
fancy_line "$(get_glyph "$type")" "$key" "$label" | |
unset A | |
} | |
done <<< "$dir_content" | |
printf -v keys[.] "BCK\tMain" #Add back button to keys list as a special, always-there case. | |
fancy_line "$GLYPH_BCK" "." "Back" #Finish drawing the back button (Default:[.]) | |
msgline_pos="$(get_cursorpos)" #Store current cursor position (should be below "Back") | |
#echo -ne "Hist:" "${hist[@]}" "N: ${#hist[@]} 0: ${hist[0]}" #Debug history printing. | |
#msgline_print "OK" | |
} | |
} | |
function act_back { | |
[[ $current_dir == Main ]] && exit 0 | |
local last="${hist[0]}" #"Pop" leftmost element. | |
hist=( "${hist[@]:1}" ) #Make hist a copy of hist without the leftmost element. | |
show_dir "$last" | |
} | |
function get_input { #Process input. | |
IFS= read -srN 1 _KEY #Read a single character. This is a simplistic hack and might or might not be non-portable, but seems to work in the terminals I tried it into. | |
if [[ -v keys[$_KEY] ]]; then { #Check if key exists in this directory. | |
local cmd="${keys[$_KEY]}" | |
IFS=$'\t' | |
read -ra A <<< "${cmd}" #Separate action type and command. | |
case "${A[0]}" in #Check action type. | |
CMD){ #Action command. | |
bash -c "${A[1]}" 1>&2; | |
exit $? | |
};; | |
DIR){ #Open dir command. | |
if [[ -v dirs[${A[1]}] ]]; then #Make sure directory exists first. | |
hist=( "$current_dir" "${hist[@]}" ) #Store new elements at the left side of the hist array. | |
show_dir "${A[1]}" | |
else | |
msgline_print "ERR:Directory ${A[1]} undefined" | |
fi | |
};; | |
BCK) act_back ;; | |
*) echo "ERR:(${A[0]})";; #Unknown type, panic. | |
esac | |
} | |
else | |
#TODO: If no valid key is pressed do nothing or re-show current directory. | |
show_dir "$current_dir" | |
fi | |
} | |
function main { | |
# Save screen contents, then clear screen. | |
echo -e '\033[?47h' # save screen | |
clear | |
[[ -z $1 ]] && { echo "No blezz config specified. Exiting."; exit 1; } | |
load_blezz "$1" #Now load config. | |
show_dir "${2:-Main}" #Load dir if specified, if not default to Main. | |
while :; do | |
get_input | |
done | |
} | |
main "$@" |
Minor update so it doesn't try to enter undefined dirs. Also a fancier display for output messages, to be used later.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated for shellcheck compliance.