]> src.twobees.de Git - dotfiles.git/blob - stow/oh-my-zsh/.oh-my-zsh/plugins/wd/wd.sh
initial
[dotfiles.git] / stow / oh-my-zsh / .oh-my-zsh / plugins / wd / wd.sh
1 #!/bin/zsh
2
3 # WARP DIRECTORY
4 # ==============
5 # Jump to custom directories in terminal
6 # because `cd` takes too long...
7 #
8 # @github.com/mfaerevaag/wd
9
10 # version
11 readonly WD_VERSION=0.5.0
12
13 # colors
14 readonly WD_BLUE="\033[96m"
15 readonly WD_GREEN="\033[92m"
16 readonly WD_YELLOW="\033[93m"
17 readonly WD_RED="\033[91m"
18 readonly WD_NOC="\033[m"
19
20 ## functions
21
22 # helpers
23 wd_yesorno()
24 {
25     # variables
26     local question="${1}"
27     local prompt="${question} "
28     local yes_RETVAL="0"
29     local no_RETVAL="3"
30     local RETVAL=""
31     local answer=""
32
33     # read-eval loop
34     while true ; do
35         printf $prompt
36         read -r answer
37
38         case ${answer:=${default}} in
39             "Y"|"y"|"YES"|"yes"|"Yes" )
40                 RETVAL=${yes_RETVAL} && \
41                     break
42                 ;;
43             "N"|"n"|"NO"|"no"|"No" )
44                 RETVAL=${no_RETVAL} && \
45                     break
46                 ;;
47             * )
48                 echo "Please provide a valid answer (y or n)"
49                 ;;
50         esac
51     done
52
53     return ${RETVAL}
54 }
55
56 wd_print_msg()
57 {
58     if [[ -z $wd_quiet_mode ]]
59     then
60         local color=$1
61         local msg=$2
62
63         if [[ $color == "" || $msg == "" ]]
64         then
65             print " ${WD_RED}*${WD_NOC} Could not print message. Sorry!"
66         else
67             print " ${color}*${WD_NOC} ${msg}"
68         fi
69     fi
70 }
71
72 wd_print_usage()
73 {
74     command cat <<- EOF
75 Usage: wd [command] [point]
76
77 Commands:
78     <point>         Warps to the directory specified by the warp point
79     <point> <path>  Warps to the directory specified by the warp point with path appended
80     add <point>     Adds the current working directory to your warp points
81     add             Adds the current working directory to your warp points with current directory's name
82     rm <point>      Removes the given warp point
83     rm              Removes the given warp point with current directory's name
84     show <point>    Print path to given warp point
85     show            Print warp points to current directory
86     list            Print all stored warp points
87     ls  <point>     Show files from given warp point (ls)
88     path <point>    Show the path to given warp point (pwd)
89     clean           Remove points warping to nonexistent directories (will prompt unless --force is used)
90
91     -v | --version  Print version
92     -d | --debug    Exit after execution with exit codes (for testing)
93     -c | --config   Specify config file (default ~/.warprc)
94     -q | --quiet    Suppress all output
95     -f | --force    Allows overwriting without warning (for add & clean)
96
97     help            Show this extremely helpful text
98 EOF
99 }
100
101 wd_exit_fail()
102 {
103     local msg=$1
104
105     wd_print_msg "$WD_RED" "$msg"
106     WD_EXIT_CODE=1
107 }
108
109 wd_exit_warn()
110 {
111     local msg=$1
112
113     wd_print_msg "$WD_YELLOW" "$msg"
114     WD_EXIT_CODE=1
115 }
116
117 wd_getdir()
118 {
119     local name_arg=$1
120
121     point=$(wd_show "$name_arg")
122     dir=${point:28+$#name_arg+7}
123
124     if [[ -z $name_arg ]]; then
125         wd_exit_fail "You must enter a warp point"
126         break
127     elif [[ -z $dir ]]; then
128         wd_exit_fail "Unknown warp point '${name_arg}'"
129         break
130     fi
131 }
132
133 # core
134
135 wd_warp()
136 {
137     local point=$1
138     local sub=$2
139
140     if [[ $point =~ "^\.+$" ]]
141     then
142         if [[ $#1 < 2 ]]
143         then
144             wd_exit_warn "Warping to current directory?"
145         else
146             (( n = $#1 - 1 ))
147             cd -$n > /dev/null
148         fi
149     elif [[ ${points[$point]} != "" ]]
150     then
151         if [[ $sub != "" ]]
152         then
153             cd ${points[$point]/#\~/$HOME}/$sub
154         else
155             cd ${points[$point]/#\~/$HOME}
156         fi
157     else
158         wd_exit_fail "Unknown warp point '${point}'"
159     fi
160 }
161
162 wd_add()
163 {
164     local point=$1
165     local force=$2
166
167     if [[ $point == "" ]]
168     then
169         point=$(basename "$PWD")
170     fi
171
172     if [[ $point =~ "^[\.]+$" ]]
173     then
174         wd_exit_fail "Warp point cannot be just dots"
175     elif [[ $point =~ "[[:space:]]+" ]]
176     then
177         wd_exit_fail "Warp point should not contain whitespace"
178     elif [[ $point =~ : ]] || [[ $point =~ / ]]
179     then
180         wd_exit_fail "Warp point contains illegal character (:/)"
181     elif [[ ${points[$point]} == "" ]] || [ ! -z "$force" ]
182     then
183         wd_remove "$point" > /dev/null
184         printf "%q:%s\n" "${point}" "${PWD/#$HOME/~}" >> "$WD_CONFIG"
185         if (whence sort >/dev/null); then
186             local config_tmp=$(mktemp "${TMPDIR:-/tmp}/wd.XXXXXXXXXX")
187             # use 'cat' below to ensure we respect $WD_CONFIG as a symlink
188             command sort -o "${config_tmp}" "$WD_CONFIG" && command cat "${config_tmp}" > "$WD_CONFIG" && command rm "${config_tmp}"
189         fi
190
191         wd_export_static_named_directories
192
193         wd_print_msg "$WD_GREEN" "Warp point added"
194
195         # override exit code in case wd_remove did not remove any points
196         # TODO: we should handle this kind of logic better
197         WD_EXIT_CODE=0
198     else
199         wd_exit_warn "Warp point '${point}' already exists. Use 'add --force' to overwrite."
200     fi
201 }
202
203 wd_remove()
204 {
205     local point_list=$1
206
207     if [[ "$point_list" == "" ]]
208     then
209         point_list=$(basename "$PWD")
210     fi
211
212     for point_name in $point_list ; do
213         if [[ ${points[$point_name]} != "" ]]
214         then
215             local config_tmp=$(mktemp "${TMPDIR:-/tmp}/wd.XXXXXXXXXX")
216             # Copy and delete in two steps in order to preserve symlinks
217             if sed -n "/^${point_name}:.*$/!p" "$WD_CONFIG" > "$config_tmp" && command cp "$config_tmp" "$WD_CONFIG" && command rm "$config_tmp"
218             then
219                 wd_print_msg "$WD_GREEN" "Warp point removed"
220             else
221                 wd_exit_fail "Something bad happened! Sorry."
222             fi
223         else
224             wd_exit_fail "Warp point was not found"
225         fi
226     done
227 }
228
229 wd_list_all()
230 {
231     wd_print_msg "$WD_BLUE" "All warp points:"
232
233     entries=$(sed "s:${HOME}:~:g" "$WD_CONFIG")
234
235     max_warp_point_length=0
236     while IFS= read -r line
237     do
238         arr=(${(s,:,)line})
239         key=${arr[1]}
240
241         length=${#key}
242         if [[ length -gt max_warp_point_length ]]
243         then
244             max_warp_point_length=$length
245         fi
246     done <<< "$entries"
247
248     while IFS= read -r line
249     do
250         if [[ $line != "" ]]
251         then
252             arr=(${(s,:,)line})
253             key=${arr[1]}
254             val=${arr[2]}
255
256             if [[ -z $wd_quiet_mode ]]
257             then
258                 printf "%${max_warp_point_length}s  ->  %s\n" "$key" "$val"
259             fi
260         fi
261     done <<< "$entries"
262 }
263
264 wd_ls()
265 {
266     wd_getdir "$1"
267     ls "${dir/#\~/$HOME}"
268 }
269
270 wd_path()
271 {
272     wd_getdir "$1"
273     echo "$(echo "$dir" | sed "s:~:${HOME}:g")"
274 }
275
276 wd_show()
277 {
278     local name_arg=$1
279     # if there's an argument we look up the value
280     if [[ -n $name_arg ]]
281     then
282         if [[ -z $points[$name_arg] ]]
283         then
284             wd_print_msg "$WD_BLUE" "No warp point named $name_arg"
285         else
286             wd_print_msg "$WD_GREEN" "Warp point: ${WD_GREEN}$name_arg${WD_NOC} -> $points[$name_arg]"
287         fi
288     else
289         # hax to create a local empty array
290         local wd_matches
291         wd_matches=()
292         # do a reverse lookup to check whether PWD is in $points
293         PWD="${PWD/$HOME/~}"
294         if [[ ${points[(r)$PWD]} == "$PWD" ]]
295         then
296             for name in ${(k)points}
297             do
298                 if [[ $points[$name] == "$PWD" ]]
299                 then
300                     wd_matches[$(($#wd_matches+1))]=$name
301                 fi
302             done
303
304             wd_print_msg "$WD_BLUE" "$#wd_matches warp point(s) to current directory: ${WD_GREEN}$wd_matches${WD_NOC}"
305         else
306             wd_print_msg "$WD_YELLOW" "No warp point to $(echo "$PWD" | sed "s:$HOME:~:")"
307         fi
308     fi
309 }
310
311 wd_clean() {
312     local force=$1
313     local count=0
314     local wd_tmp=""
315
316     while read -r line
317     do
318         if [[ $line != "" ]]
319         then
320             arr=(${(s,:,)line})
321             key=${arr[1]}
322             val=${arr[2]}
323
324             if [ -d "${val/#\~/$HOME}" ]
325             then
326                 wd_tmp=$wd_tmp"\n"`echo "$line"`
327             else
328                 wd_print_msg "$WD_YELLOW" "Nonexistent directory: ${key} -> ${val}"
329                 count=$((count+1))
330             fi
331         fi
332     done < "$WD_CONFIG"
333
334     if [[ $count -eq 0 ]]
335     then
336         wd_print_msg "$WD_BLUE" "No warp points to clean, carry on!"
337     else
338         if [ ! -z "$force" ] || wd_yesorno "Removing ${count} warp points. Continue? (y/n)"
339         then
340             echo "$wd_tmp" >! "$WD_CONFIG"
341             wd_print_msg "$WD_GREEN" "Cleanup complete. ${count} warp point(s) removed"
342         else
343             wd_print_msg "$WD_BLUE" "Cleanup aborted"
344         fi
345     fi
346 }
347
348 wd_export_static_named_directories() {
349   if [[ ! -z $WD_EXPORT ]]
350   then
351     command grep '^[0-9a-zA-Z_-]\+:' "$WD_CONFIG" | sed -e "s,~,$HOME," -e 's/:/=/' | while read -r warpdir ; do
352         hash -d "$warpdir"
353     done
354   fi
355 }
356
357 local WD_CONFIG=${WD_CONFIG:-$HOME/.warprc}
358 local WD_QUIET=0
359 local WD_EXIT_CODE=0
360 local WD_DEBUG=0
361
362 # Parse 'meta' options first to avoid the need to have them before
363 # other commands. The `-D` flag consumes recognized options so that
364 # the actual command parsing won't be affected.
365
366 zparseopts -D -E \
367     c:=wd_alt_config -config:=wd_alt_config \
368     q=wd_quiet_mode -quiet=wd_quiet_mode \
369     v=wd_print_version -version=wd_print_version \
370     d=wd_debug_mode -debug=wd_debug_mode \
371     f=wd_force_mode -force=wd_force_mode
372
373 if [[ ! -z $wd_print_version ]]
374 then
375     echo "wd version $WD_VERSION"
376 fi
377
378 if [[ ! -z $wd_alt_config ]]
379 then
380     WD_CONFIG=$wd_alt_config[2]
381 fi
382
383 # check if config file exists
384 if [ ! -e "$WD_CONFIG" ]
385 then
386     # if not, create config file
387     touch "$WD_CONFIG"
388 else
389     wd_export_static_named_directories
390 fi
391
392 # load warp points
393 typeset -A points
394 while read -r line
395 do
396     arr=(${(s,:,)line})
397     key=${arr[1]}
398     # join the rest, in case the path contains colons
399     val=${(j,:,)arr[2,-1]}
400
401     points[$key]=$val
402 done < "$WD_CONFIG"
403
404 # get opts
405 args=$(getopt -o a:r:c:lhs -l add:,rm:,clean,list,ls:,path:,help,show -- $*)
406
407 # check if no arguments were given, and that version is not set
408 if [[ ($? -ne 0 || $#* -eq 0) && -z $wd_print_version ]]
409 then
410     wd_print_usage
411
412 # check if config file is writeable
413 elif [ ! -w "$WD_CONFIG" ]
414 then
415     # do nothing
416     # can't run `exit`, as this would exit the executing shell
417     wd_exit_fail "\'$WD_CONFIG\' is not writeable."
418
419 else
420     # parse rest of options
421     local wd_o
422     for wd_o
423     do
424         case "$wd_o"
425             in
426             "-a"|"--add"|"add")
427                 wd_add "$2" "$wd_force_mode"
428                 break
429                 ;;
430             "-e"|"export")
431                 wd_export_static_named_directories
432                 break
433                 ;;
434             "-r"|"--remove"|"rm")
435                 # Passes all the arguments as a single string separated by whitespace to wd_remove
436                 wd_remove "${@:2}"
437                 break
438                 ;;
439             "-l"|"list")
440                 wd_list_all
441                 break
442                 ;;
443             "-ls"|"ls")
444                 wd_ls "$2"
445                 break
446                 ;;
447             "-p"|"--path"|"path")
448                 wd_path "$2"
449                 break
450                 ;;
451             "-h"|"--help"|"help")
452                 wd_print_usage
453                 break
454                 ;;
455             "-s"|"--show"|"show")
456                 wd_show "$2"
457                 break
458                 ;;
459             "-c"|"--clean"|"clean")
460                 wd_clean "$wd_force_mode"
461                 break
462                 ;;
463             *)
464                 wd_warp "$wd_o" "$2"
465                 break
466                 ;;
467             --)
468                 break
469                 ;;
470         esac
471     done
472 fi
473
474 ## garbage collection
475 # if not, next time warp will pick up variables from this run
476 # remember, there's no sub shell
477
478 unset wd_warp
479 unset wd_add
480 unset wd_remove
481 unset wd_show
482 unset wd_list_all
483 unset wd_print_msg
484 unset wd_yesorno
485 unset wd_print_usage
486 unset wd_alt_config
487 unset wd_quiet_mode
488 unset wd_print_version
489 unset wd_export_static_named_directories
490 unset wd_o
491
492 unset args
493 unset points
494 unset val &> /dev/null # fixes issue #1
495
496 if [[ -n $wd_debug_mode ]]
497 then
498     exit $WD_EXIT_CODE
499 else
500     unset wd_debug_mode
501 fi