1#!/usr/local/bin/bash 2VERSION="0.0.2" 3 4# with tool 5# usage: with <program> 6# opens an interactive shell instance that automatically prefixes all subsequent commands with program name 7 8# bind TAB to completion function within the script 9bind -x '"\C-i": "with_completion"' &> /dev/null 10 11print_version() 12{ 13 echo "with, version $VERSION" 14 exit 0 15} 16 17print_options() 18{ 19 echo " -h, --help : Display command help" 20 echo " -v, --version: Display the currently installed version of with" 21} 22 23print_usage() 24{ 25 print """ 26 USAGE: 27 with <prefix> 28 29 Prefix can be any string with a valid executable. 30 """ 31 exit 0 32} 33 34#add options here, such as -h, -v 35declare -a prefix 36prefix=( "$@" ) 37 38case ${prefix[*]} in 39 "" ) 40 echo "Missing arguments." 41 echo "usage: with <program>" 42 exit 1;; 43 "-v"|"--version") 44 print_version;; 45 "-h"|"--help") 46 print_help;; 47 -*|--*) 48 echo "Unrecognised option:" ${prefix[*]} 49 print_options 50 exit 1;; 51esac 52 53pmpt=${prefix[*]} 54 55setup() 56{ 57 58 # source bash completions 59 [ -f /etc/bash_completion ] && source /etc/bash_completion 60 61 BASH_COMPLETION_DEFAULT_DIR=/usr/share/bash-completion/completions 62 for completion_file in $BASH_COMPLETION_DEFAULT_DIR/* $BASH_COMPLETION_COMPAT_DIR/* 63 do 64 . "$completion_file" &> /dev/null 65 done 66 67 # initialise history file 68 touch /tmp/with_history 69 70 # set up colour codes 71 __blk="$(tput setaf 0)" 72 __red="$(tput setaf 1)" 73 __grn="$(tput setaf 2)" 74 __yel="$(tput setaf 3)" 75 __blu="$(tput setaf 4)" 76 __mag="$(tput setaf 5)" 77 __cyn="$(tput setaf 6)" 78 __wht="$(tput setaf 7)" 79 80 __bold_blk="$__bold$__blk" 81 __bold_red="$__bold$__red" 82 __bold_grn="$__bold$__grn" 83 __bold_yel="$__bold$__yel" 84 __bold_blu="$__bold$__blu" 85 __bold_mag="$__bold$__mag" 86 __bold_cyn="$__bold$__cyn" 87 __bold_wht="$__bold$__wht" 88 89 __on_blk="$(tput setab 0)" 90 __on_red="$(tput setab 1)" 91 __on_grn="$(tput setab 2)" 92 __on_yel="$(tput setab 3)" 93 __on_blu="$(tput setab 4)" 94 __on_mag="$(tput setab 5)" 95 __on_cyn="$(tput setab 6)" 96 __on_wht="$(tput setab 7)" 97 98 # color reset 99 __nc="$(tput sgr0)" 100 101 __blk() { echo -n "$__blk$*$__nc"; } 102 __red() { echo -n "$__red$*$__nc"; } 103 __grn() { echo -n "$__grn$*$__nc"; } 104 __yel() { echo -n "$__yel$*$__nc"; } 105 __blu() { echo -n "$__blu$*$__nc"; } 106 __mag() { echo -n "$__mag$*$__nc"; } 107 __cyn() { echo -n "$__cyn$*$__nc"; } 108 __wht() { echo -n "$__wht$*$__nc"; } 109 110 __bold_blk() { echo -n "$__bold_blk$*$__nc"; } 111 __bold_red() { echo -n "$__bold_red$*$__nc"; } 112 __bold_grn() { echo -n "$__bold_grn$*$__nc"; } 113 __bold_yel() { echo -n "$__bold_yel$*$__nc"; } 114 __bold_blu() { echo -n "$__bold_blu$*$__nc"; } 115 __bold_mag() { echo -n "$__bold_mag$*$__nc"; } 116 __bold_cyn() { echo -n "$__bold_cyn$*$__nc"; } 117 __bold_wht() { echo -n "$__bold_wht$*$__nc"; } 118 119 __on_blk() { echo -n "$__on_blk$*$__nc"; } 120 __on_red() { echo -n "$__on_red$*$__nc"; } 121 __on_grn() { echo -n "$__on_grn$*$__nc"; } 122 __on_yel() { echo -n "$__on_yel$*$__nc"; } 123 __on_blu() { echo -n "$__on_blu$*$__nc"; } 124 __on_mag() { echo -n "$__on_mag$*$__nc"; } 125 __on_cyn() { echo -n "$__on_cyn$*$__nc"; } 126 __on_wht() { echo -n "$__on_wht$*$__nc"; } 127} 128 129__print_prompt() { 130 __prefix="${prefix[*]}" print_prompt "$@" 131} 132 133print_prompt() { 134 135 # TODO: change name to correct 136 hashdollar() { 137 (( UID )) && echo '$' \ 138 || echo '#' 139 } 140 141 colorise_prompt() { 142 local to_be_replaced=(blk red grn yel blu mag cyn wht) 143 144 local SED_COMMAND_LINE=('sed' '-E') 145 146 for color in "${to_be_replaced[@]}"; do 147 SED_COMMAND_LINE+=( 148 '-e' "s/%on_$color%/$(eval echo "\$__on_$color")/g" 149 '-e' "s/%bold_$color%/$(eval echo "\$__bold_$color")/g" 150 '-e' "s/%$color%/$(eval echo "\$__$color")/g" 151 ) 152 done 153 154 "${SED_COMMAND_LINE[@]}" -e "s/%nc%/$__nc/g" 155 } 156 local __escaped_prefix=$(echo -n "$__prefix" | sed -e 's/\./\\./g' -e 's/\//\\\//g') 157 echo -n "$*" | colorise_prompt | sed -E -e "s/%prefix%/$__escaped_prefix/g" \ 158 -e "s/\\$/$(hashdollar)/g" 159} 160 161with_completion() 162{ 163 # print readline's prompt for visual separation 164 if [ "$#" -eq 0 ]; then 165 echo "$(__print_prompt "$PROMPT_FORMAT")$READLINE_LINE" 166 fi 167 168 # remove part after readline cursor from completion line 169 local completion_line completion_word 170 completion_line="${READLINE_LINE:0:READLINE_POINT}" 171 completion_word="${completion_line##* }" 172 173 # set completion cursor according to pmpt length 174 COMP_POINT=$((${#pmpt}+${#completion_line}+1)) 175 COMP_WORDBREAKS="\n\"'><=;|&(:" 176 COMP_LINE="$pmpt $completion_line" 177 COMP_WORDS=($COMP_LINE) 178 179 # TODO: the purpose of these variables is still unclear 180 # COMP_TYPE=63 181 # COMP_KEY=9 182 183 # get index of word to be completed 184 local whitespaces_count escaped_whitespaces_count 185 whitespaces_count=$(echo "$COMP_LINE" | grep -o ' ' | wc -l) 186 escaped_whitespaces_count=$(echo "$COMP_LINE" | grep -o '\\ ' | wc -l) 187 COMP_CWORD=$((whitespaces_count-escaped_whitespaces_count)) 188 189 # get sourced completion command 190 local program_name complete_command 191 program_name=${COMP_WORDS[0]} 192 program_name=$(basename "$program_name") 193 complete_command=$(complete -p | grep " ${program_name}$") 194 195 COMPREPLY=() 196 197 # execute appropriate complete actions 198 if [[ "$complete_command" =~ \ -F\ ]] 199 then 200 local complete_function 201 complete_function=$(awk '{for(i=1;i<=NF;i++) if ($i=="-F") print $(i+1)}' <(echo "$complete_command")) 202 203 # generate completions 204 $complete_function 205 else 206 # construct compgen command 207 local compgen_command 208 compgen_command=$(echo "$complete_command" | sed 's/^complete/compgen/g') 209 compgen_command="${compgen_command//$program_name/$completion_word}" 210 211 # generate completions 212 COMPREPLY=($($compgen_command)) 213 fi 214 215 # get commmon prefix of available completions 216 local completions_prefix readline_prefix readline_suffix 217 completions_prefix=$(printf "%s\n" "${COMPREPLY[@]}" | \ 218 sed -e '$!{N;s/^\(.*\).*\n\1.*$/\1\n\1/;D;}' | xargs) 219 readline_prefix="${READLINE_LINE:0:READLINE_POINT}" 220 readline_suffix="${READLINE_LINE:READLINE_POINT}" 221 # remove the word to be completed 222 readline_prefix=$(sed s/'\w*$'// <(echo "$readline_prefix") | xargs) 223 224 READLINE_LINE="" 225 if [[ "$readline_prefix" != "" ]]; then 226 READLINE_LINE="$readline_prefix " 227 fi 228 229 READLINE_LINE="$READLINE_LINE$completions_prefix" 230 # adjust readline cursor position 231 READLINE_POINT=$((${#READLINE_LINE}+1)) 232 233 if [[ "$readline_suffix" != "" ]]; then 234 READLINE_LINE="$READLINE_LINE $readline_suffix" 235 fi 236 237 local completions_count display_all 238 completions_count=${#COMPREPLY[@]} 239 display_all="y" 240 if [[ $completions_count -eq 1 ]]; then 241 READLINE_LINE=$(echo "$READLINE_LINE" | xargs) 242 READLINE_LINE="$READLINE_LINE " 243 return 244 elif [[ $completions_count -gt 80 ]]; then 245 echo -en "Display all $completions_count possibilities? (y or n) " 246 read -N 1 display_all 247 echo "$display_all" 248 fi 249 250 if [[ "$display_all" = "y" ]]; then 251 for completion in "${COMPREPLY[@]}"; do echo "$completion"; done | column 252 fi 253} 254 255finish() 256{ 257 # save history to bash history 258 if [ -f ~/.bash_history ]; then 259 cat /tmp/with_history >> ~/.bash_history 260 fi 261 rm /tmp/with_history 262} 263 264drop_with() 265{ 266 if [ ${#prefix[@]} -gt 1 ] 267 then 268 prefix=( "${prefix[@]:0:${#prefix[@]}-1}" ) 269 else 270 exit 0 271 fi 272} 273 274add_with() 275{ 276 # separate into white space 277 # FIXME: foo "bar baz" should add two elements not one 278 IFS=' ' read -r -a parse_array <<< "$@" 279 prefix=( "${prefix[@]}" "${parse_array[@]}" ) 280} 281 282run_with() 283{ 284 while IFS="" read -r -e -d $'\n' -p "$(__print_prompt "$PROMPT_FORMAT")" options; do 285 history -s "$options" > /dev/null 2>&1 286 287 curr_command="$(echo "$options" | { read -r first rest ; echo "$first" ; })" 288 case $curr_command in 289 "" ) 290 # null case: run prefix 291 ${prefix[*]} ;; 292 !* ) 293 # replace current command 294 drop_with 295 parsed=${options#"!"} 296 add_with "$parsed" ;; 297 -* ) 298 # remove with 299 parsed=${options#"-"} 300 if [ -z "$parsed" ]; then 301 drop_with 302 else 303 for ((x=0; x<$((parsed)); x++)) { 304 drop_with 305 } 306 fi 307 pmpt="${prefix[*]}" ;; 308 +* ) 309 # nesting withs 310 parsed=${options#"+"} 311 add_with "$parsed" 312 pmpt="${prefix[*]}" ;; 313 :* ) 314 # shell command 315 parsed=${options#":"} 316 if [ "$parsed" = "q" ]; then 317 exit 0 318 fi 319 IFS=' ' read -r -a parsed_array <<< "$parsed" 320 echo "${parsed_array[@]}" >> /tmp/with_history 321 eval "${parsed_array[@]}" ;; 322 * ) 323 # prepend prefix to command 324 echo "${prefix[*]} ${options}" >> /tmp/with_history 325 eval "${prefix[*]} ${options}" 326 esac 327 done 328} 329 330main() 331{ 332 trap finish exit 333 [ "$PROMPT_FORMAT" ] || PROMPT_FORMAT+='%yel%$%nc% ' \ 334 PROMPT_FORMAT+='%cyn%%prefix%%nc% ' \ 335 PROMPT_FORMAT+='%wht%>%nc% ' 336 337 HISTCONTROL=ignoreboth 338 339 # run script setup 340 setup 341 342 if [ "$1" == "" ]; then 343 print_usage 344 elif ! type "$1" > /dev/null 2>&1; then 345 echo "error: \"$1\" is not a valid executable" 346 exit 1 347 fi 348 349 while true ; do 350 run_with 351 done 352} 353 354main "$@" 355