wd.sh 11.9 KB
Newer Older
Markus Færevaag's avatar
Markus Færevaag committed
1
2
#!/bin/zsh

3
4
# WARP DIRECTORY
# ==============
Markus Færevaag's avatar
Markus Færevaag committed
5
6
7
8
9
# Jump to custom directories in terminal
# because `cd` takes too long...
#
# @github.com/mfaerevaag/wd

10
# version
11
readonly WD_VERSION=0.4.6
Markus Færevaag's avatar
Markus Færevaag committed
12

13
# colors
14
15
16
17
18
readonly WD_BLUE="\033[96m"
readonly WD_GREEN="\033[92m"
readonly WD_YELLOW="\033[93m"
readonly WD_RED="\033[91m"
readonly WD_NOC="\033[m"
Markus Færevaag's avatar
Markus Færevaag committed
19

20
## functions
Markus Færevaag's avatar
Markus Færevaag committed
21

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# helpers
wd_yesorno()
{
    # variables
    local question="${1}"
    local prompt="${question} "
    local yes_RETVAL="0"
    local no_RETVAL="3"
    local RETVAL=""
    local answer=""

    # read-eval loop
    while true ; do
        printf $prompt
        read -r answer

        case ${answer:=${default}} in
39
            "Y"|"y"|"YES"|"yes"|"Yes" )
40
41
42
                RETVAL=${yes_RETVAL} && \
                    break
                ;;
43
            "N"|"n"|"NO"|"no"|"No" )
44
45
46
47
48
49
50
51
                RETVAL=${no_RETVAL} && \
                    break
                ;;
            * )
                echo "Please provide a valid answer (y or n)"
                ;;
        esac
    done
52

53
54
    return ${RETVAL}
}
55

56
57
58
59
60
61
wd_print_msg()
{
    if [[ -z $wd_quiet_mode ]]
    then
        local color=$1
        local msg=$2
Markus Færevaag's avatar
Markus Færevaag committed
62

63
64
65
66
67
68
69
70
        if [[ $color == "" || $msg == "" ]]
        then
            print " ${WD_RED}*${WD_NOC} Could not print message. Sorry!"
        else
            print " ${color}*${WD_NOC} ${msg}"
        fi
    fi
}
Markus Færevaag's avatar
Markus Færevaag committed
71

72
73
74
wd_print_usage()
{
    cat <<- EOF
75
Usage: wd [command] [point]
Markus Færevaag's avatar
Markus Færevaag committed
76

77
Commands:
78
79
    <point>         Warps to the directory specified by the warp point
    <point> <path>  Warps to the directory specified by the warp point with path appended
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
    add <point>     Adds the current working directory to your warp points
    add             Adds the current working directory to your warp points with current directory's name
    add! <point>    Overwrites existing warp point
    add!            Overwrites existing warp point with current directory's name
    rm <point>      Removes the given warp point
    rm              Removes the given warp point with current directory's name
    show <point>    Print path to given warp point
    show            Print warp points to current directory
    list            Print all stored warp points
    ls  <point>     Show files from given warp point (ls)
    path <point>    Show the path to given warp point (pwd)
    clean!          Remove points warping to nonexistent directories

    -v | --version  Print version
    -d | --debug    Exit after execution with exit codes (for testing)
    -c | --config   Specify config file (default ~/.warprc)
    -q | --quiet    Suppress all output

    help            Show this extremely helpful text
99
100
101
102
103
104
105
EOF
}

wd_exit_fail()
{
    local msg=$1

Markus Faerevaag's avatar
Markus Faerevaag committed
106
    wd_print_msg $WD_RED $msg
107
108
109
110
111
112
113
114
115
116
117
    WD_EXIT_CODE=1
}

wd_exit_warn()
{
    local msg=$1

    wd_print_msg $WD_YELLOW $msg
    WD_EXIT_CODE=1
}

Markus Faerevaag's avatar
Markus Faerevaag committed
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
wd_getdir()
{
    local name_arg=$1

    point=$(wd_show $name_arg)
    dir=${point:28+$#name_arg+7}

    if [[ -z $name_arg ]]; then
        wd_exit_fail "You must enter a warp point"
        break
    elif [[ -z $dir ]]; then
        wd_exit_fail "Unknown warp point '${name_arg}'"
        break
    fi
}

134
# core
Markus Færevaag's avatar
Markus Færevaag committed
135
136
137

wd_warp()
{
138
    local point=$1
139
    local sub=$2
140
141

    if [[ $point =~ "^\.+$" ]]
Markus Færevaag's avatar
Markus Færevaag committed
142
    then
143
        if [[ $#1 < 2 ]]
Markus Færevaag's avatar
Markus Færevaag committed
144
        then
145
            wd_exit_warn "Warping to current directory?"
Markus Færevaag's avatar
Markus Færevaag committed
146
147
148
149
        else
            (( n = $#1 - 1 ))
            cd -$n > /dev/null
        fi
150
    elif [[ ${points[$point]} != "" ]]
Markus Færevaag's avatar
Markus Færevaag committed
151
    then
152
153
154
155
156
157
        if [[ $sub != "" ]]
        then
            cd ${points[$point]/#\~/$HOME}/$sub
        else
            cd ${points[$point]/#\~/$HOME}
        fi
Markus Færevaag's avatar
Markus Færevaag committed
158
    else
159
        wd_exit_fail "Unknown warp point '${point}'"
Markus Færevaag's avatar
Markus Færevaag committed
160
161
162
163
164
    fi
}

wd_add()
{
165
166
167
    local force=$1
    local point=$2

168
169
170
171
172
    if [[ $point == "" ]]
    then
        point=$(basename $PWD)
    fi

173
174
    if [[ $point =~ "^[\.]+$" ]]
    then
175
        wd_exit_fail "Warp point cannot be just dots"
Markus Faerevaag's avatar
Markus Faerevaag committed
176
    elif [[ $point =~ "[[:space:]]+" ]]
Markus Færevaag's avatar
Markus Færevaag committed
177
    then
178
        wd_exit_fail "Warp point should not contain whitespace"
179
    elif [[ $point == *:* ]]
Markus Færevaag's avatar
Markus Færevaag committed
180
    then
181
        wd_exit_fail "Warp point cannot contain colons"
182
    elif [[ ${points[$point]} == "" ]] || $force
183
184
    then
        wd_remove $point > /dev/null
185
        printf "%q:%s\n" "${point}" "${PWD/#$HOME/~}" >> $WD_CONFIG
Marc Cornellà's avatar
Marc Cornellà committed
186
187
188
189
190
        if (whence sort >/dev/null); then
            local config_tmp=$(mktemp "${TMPDIR:-/tmp}/wd.XXXXXXXXXX")
            # use 'cat' below to ensure we respect $WD_CONFIG as a symlink
            sort -o "${config_tmp}" $WD_CONFIG  && cat "${config_tmp}" > $WD_CONFIG && rm "${config_tmp}"
        fi
191

192
193
        wd_export_static_named_directories

194
        wd_print_msg $WD_GREEN "Warp point added"
195

196
197
198
        # override exit code in case wd_remove did not remove any points
        # TODO: we should handle this kind of logic better
        WD_EXIT_CODE=0
Markus Færevaag's avatar
Markus Færevaag committed
199
    else
200
        wd_exit_warn "Warp point '${point}' already exists. Use 'add!' to overwrite."
Markus Færevaag's avatar
Markus Færevaag committed
201
202
203
204
205
    fi
}

wd_remove()
{
206
207
    local point=$1

208
209
210
211
212
    if [[ $point == "" ]]
    then
        point=$(basename $PWD)
    fi

213
    if [[ ${points[$point]} != "" ]]
Markus Færevaag's avatar
Markus Færevaag committed
214
    then
215
216
217
        local config_tmp=$(mktemp "${TMPDIR:-/tmp}/wd.XXXXXXXXXX")
        # Copy and delete in two steps in order to preserve symlinks
        if sed -n "/^${point}:.*$/!p" $WD_CONFIG > $config_tmp && cp $config_tmp $WD_CONFIG && rm $config_tmp
Markus Færevaag's avatar
Markus Færevaag committed
218
        then
219
            wd_print_msg $WD_GREEN "Warp point removed"
Markus Færevaag's avatar
Markus Færevaag committed
220
        else
221
            wd_exit_fail "Something bad happened! Sorry."
Markus Færevaag's avatar
Markus Færevaag committed
222
223
        fi
    else
224
        wd_exit_fail "Warp point was not found"
Markus Færevaag's avatar
Markus Færevaag committed
225
226
227
228
229
    fi
}

wd_list_all()
{
230
    wd_print_msg $WD_BLUE "All warp points:"
231

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
    entries=$(sed "s:${HOME}:~:g" $WD_CONFIG)

    max_warp_point_length=0
    while IFS= read -r line
    do
        arr=(${(s,:,)line})
        key=${arr[1]}

        length=${#key}
        if [[ length -gt max_warp_point_length ]]
        then
            max_warp_point_length=$length
        fi
    done <<< $entries

247
    while IFS= read -r line
Markus Færevaag's avatar
Markus Færevaag committed
248
249
250
251
252
253
254
    do
        if [[ $line != "" ]]
        then
            arr=(${(s,:,)line})
            key=${arr[1]}
            val=${arr[2]}

255
256
            if [[ -z $wd_quiet_mode ]]
            then
257
                printf "%${max_warp_point_length}s  ->  %s\n" $key $val
258
            fi
Markus Færevaag's avatar
Markus Færevaag committed
259
        fi
260
    done <<< $entries
261
262
}

Markus Faerevaag's avatar
Markus Faerevaag committed
263
264
265
wd_ls()
{
    wd_getdir $1
266
    ls ${dir/#\~/$HOME}
Markus Faerevaag's avatar
Markus Faerevaag committed
267
268
269
270
271
272
273
274
}

wd_path()
{
    wd_getdir $1
    echo $(echo $dir | sed "s:${HOME}:~:g")
}

275
276
wd_show()
{
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
    local name_arg=$1
    # if there's an argument we look up the value
    if [[ ! -z $name_arg ]]
    then
        if [[ -z $points[$name_arg] ]]
        then
            wd_print_msg $WD_BLUE "No warp point named $name_arg"
        else
            wd_print_msg $WD_GREEN "Warp point: ${WD_GREEN}$name_arg${WD_NOC} -> $points[$name_arg]"
        fi
    else
        # hax to create a local empty array
        local wd_matches
        wd_matches=()
        # do a reverse lookup to check whether PWD is in $points
292
        PWD="${PWD/$HOME/~}"
293
294
295
296
297
298
299
300
301
302
303
304
        if [[ ${points[(r)$PWD]} == $PWD ]]
        then
            for name in ${(k)points}
            do
                if [[ $points[$name] == $PWD ]]
                then
                    wd_matches[$(($#wd_matches+1))]=$name
                fi
            done

            wd_print_msg $WD_BLUE "$#wd_matches warp point(s) to current directory: ${WD_GREEN}$wd_matches${WD_NOC}"
        else
305
            wd_print_msg $WD_YELLOW "No warp point to $(echo $PWD | sed "s:$HOME:~:")"
306
307
        fi
    fi
Markus Færevaag's avatar
Markus Færevaag committed
308
309
}

310
311
312
313
314
315
316
317
318
319
320
321
wd_clean() {
    local force=$1
    local count=0
    local wd_tmp=""

    while read line
    do
        if [[ $line != "" ]]
        then
            arr=(${(s,:,)line})
            key=${arr[1]}
            val=${arr[2]}
322

323
            if [ -d "${val/#\~/$HOME}" ]
324
325
326
327
328
329
330
331
332
333
            then
                wd_tmp=$wd_tmp"\n"`echo $line`
            else
                wd_print_msg $WD_YELLOW "Nonexistent directory: ${key} -> ${val}"
                count=$((count+1))
            fi
        fi
    done < $WD_CONFIG

    if [[ $count -eq 0 ]]
Markus Færevaag's avatar
Markus Færevaag committed
334
    then
335
        wd_print_msg $WD_BLUE "No warp points to clean, carry on!"
Markus Færevaag's avatar
Markus Færevaag committed
336
    else
337
338
339
340
341
342
343
        if $force || wd_yesorno "Removing ${count} warp points. Continue? (Y/n)"
        then
            echo $wd_tmp >! $WD_CONFIG
            wd_print_msg $WD_GREEN "Cleanup complete. ${count} warp point(s) removed"
        else
            wd_print_msg $WD_BLUE "Cleanup aborted"
        fi
Markus Færevaag's avatar
Markus Færevaag committed
344
345
346
    fi
}

347
348
349
wd_export_static_named_directories() {
  if [[ -z $WD_SKIP_EXPORT ]]
  then
Marc Cornellà's avatar
Marc Cornellà committed
350
351
    command grep '^[0-9a-zA-Z_-]\+:' "$WD_CONFIG" | sed -e "s,~,$HOME," -e 's/:/=/' | while read warpdir ; do
        hash -d "$warpdir"
352
353
354
355
356
    done
  fi
}

local WD_CONFIG=${WD_CONFIG:-$HOME/.warprc}
357
358
359
local WD_QUIET=0
local WD_EXIT_CODE=0
local WD_DEBUG=0
360

361
362
363
# Parse 'meta' options first to avoid the need to have them before
# other commands. The `-D` flag consumes recognized options so that
# the actual command parsing won't be affected.
Markus Færevaag's avatar
Markus Færevaag committed
364

365
366
367
368
369
zparseopts -D -E \
    c:=wd_alt_config -config:=wd_alt_config \
    q=wd_quiet_mode -quiet=wd_quiet_mode \
    v=wd_print_version -version=wd_print_version \
    d=wd_debug_mode -debug=wd_debug_mode
Markus Færevaag's avatar
Markus Færevaag committed
370

371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
if [[ ! -z $wd_print_version ]]
then
    echo "wd version $WD_VERSION"
fi

if [[ ! -z $wd_alt_config ]]
then
    WD_CONFIG=$wd_alt_config[2]
fi

# check if config file exists
if [ ! -e $WD_CONFIG ]
then
    # if not, create config file
    touch $WD_CONFIG
386
387
else
    wd_export_static_named_directories
388
389
390
391
392
393
394
395
fi

# load warp points
typeset -A points
while read -r line
do
    arr=(${(s,:,)line})
    key=${arr[1]}
396
397
    # join the rest, in case the path contains colons
    val=${(j,:,)arr[2,-1]}
398
399
400

    points[$key]=$val
done < $WD_CONFIG
Markus Færevaag's avatar
Markus Færevaag committed
401
402

# get opts
Markus Faerevaag's avatar
Markus Faerevaag committed
403
args=$(getopt -o a:r:c:lhs -l add:,rm:,clean\!,list,ls:,path:,help,show -- $*)
Markus Færevaag's avatar
Markus Færevaag committed
404

405
406
# check if no arguments were given, and that version is not set
if [[ ($? -ne 0 || $#* -eq 0) && -z $wd_print_version ]]
Markus Færevaag's avatar
Markus Færevaag committed
407
408
409
then
    wd_print_usage

410
411
    # check if config file is writeable
elif [ ! -w $WD_CONFIG ]
412
then
413
    # do nothing
414
    # can't run `exit`, as this would exit the executing shell
415
    wd_exit_fail "\'$WD_CONFIG\' is not writeable."
416
417

else
418
419

    # parse rest of options
420
421
    local wd_o
    for wd_o
Markus Færevaag's avatar
Markus Færevaag committed
422
    do
423
        case "$wd_o"
Markus Faerevaag's avatar
Markus Faerevaag committed
424
            in
425
            "-a"|"--add"|"add")
Markus Faerevaag's avatar
Markus Faerevaag committed
426
                wd_add false $2
Markus Færevaag's avatar
Markus Færevaag committed
427
428
                break
                ;;
429
            "-a!"|"--add!"|"add!")
Markus Faerevaag's avatar
Markus Faerevaag committed
430
                wd_add true $2
Markus Færevaag's avatar
Markus Færevaag committed
431
432
                break
                ;;
433
434
435
436
437
            "-e"|"export")
                wd_export_static_named_directories
                break
                ;;
            "-r"|"--remove"|"rm")
Marc Cornellà's avatar
Marc Cornellà committed
438
439
440
441
                # Loop over all arguments after "rm", separated by whitespace
                for pointname in "${@:2}" ; do
                    wd_remove $pointname
                done
Markus Færevaag's avatar
Markus Færevaag committed
442
443
                break
                ;;
444
            "-l"|"list")
Markus Faerevaag's avatar
Markus Faerevaag committed
445
                wd_list_all
Markus Færevaag's avatar
Markus Færevaag committed
446
447
                break
                ;;
448
            "-ls"|"ls")
Markus Faerevaag's avatar
Markus Faerevaag committed
449
450
451
                wd_ls $2
                break
                ;;
452
            "-p"|"--path"|"path")
Markus Faerevaag's avatar
Markus Faerevaag committed
453
454
455
                wd_path $2
                break
                ;;
456
            "-h"|"--help"|"help")
Markus Faerevaag's avatar
Markus Faerevaag committed
457
                wd_print_usage
Markus Færevaag's avatar
Markus Færevaag committed
458
459
                break
                ;;
460
            "-s"|"--show"|"show")
461
462
463
                wd_show $2
                break
                ;;
464
            "-c"|"--clean"|"clean")
465
466
467
                wd_clean false
                break
                ;;
468
            "-c!"|"--clean!"|"clean!")
469
                wd_clean true
Markus Færevaag's avatar
Markus Færevaag committed
470
471
472
                break
                ;;
            *)
473
                wd_warp $wd_o $2
Markus Færevaag's avatar
Markus Færevaag committed
474
475
                break
                ;;
Markus Faerevaag's avatar
Markus Faerevaag committed
476
477
478
479
            --)
                break
                ;;
        esac
Markus Færevaag's avatar
Markus Færevaag committed
480
481
482
483
484
485
    done
fi

## garbage collection
# if not, next time warp will pick up variables from this run
# remember, there's no sub shell
486
487
488
489
490
491
492

unset wd_warp
unset wd_add
unset wd_remove
unset wd_show
unset wd_list_all
unset wd_print_msg
493
unset wd_yesorno
494
unset wd_print_usage
495
496
497
unset wd_alt_config
unset wd_quiet_mode
unset wd_print_version
498
499
unset wd_export_static_named_directories
unset wd_o
500

Markus Faerevaag's avatar
Markus Faerevaag committed
501
unset args
502
unset points
Markus Faerevaag's avatar
Markus Faerevaag committed
503
unset val &> /dev/null # fixes issue #1
504
505
506
507
508
509
510

if [[ ! -z $wd_debug_mode ]]
then
    exit $WD_EXIT_CODE
else
    unset wd_debug_mode
fi