wd.sh 11.5 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
186

187
188
        wd_export_static_named_directories

189
        wd_print_msg $WD_GREEN "Warp point added"
190

191
192
193
        # 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
194
    else
195
        wd_exit_warn "Warp point '${point}' already exists. Use 'add!' to overwrite."
Markus Færevaag's avatar
Markus Færevaag committed
196
197
198
199
200
    fi
}

wd_remove()
{
201
202
    local point=$1

203
204
205
206
207
    if [[ $point == "" ]]
    then
        point=$(basename $PWD)
    fi

208
    if [[ ${points[$point]} != "" ]]
Markus Færevaag's avatar
Markus Færevaag committed
209
    then
210
211
212
        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
213
        then
214
            wd_print_msg $WD_GREEN "Warp point removed"
Markus Færevaag's avatar
Markus Færevaag committed
215
        else
216
            wd_exit_fail "Something bad happened! Sorry."
Markus Færevaag's avatar
Markus Færevaag committed
217
218
        fi
    else
219
        wd_exit_fail "Warp point was not found"
Markus Færevaag's avatar
Markus Færevaag committed
220
221
222
223
224
    fi
}

wd_list_all()
{
225
    wd_print_msg $WD_BLUE "All warp points:"
226

227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
    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

242
    while IFS= read -r line
Markus Færevaag's avatar
Markus Færevaag committed
243
244
245
246
247
248
249
    do
        if [[ $line != "" ]]
        then
            arr=(${(s,:,)line})
            key=${arr[1]}
            val=${arr[2]}

250
251
            if [[ -z $wd_quiet_mode ]]
            then
252
                printf "%${max_warp_point_length}s  ->  %s\n" $key $val
253
            fi
Markus Færevaag's avatar
Markus Færevaag committed
254
        fi
255
    done <<< $entries
256
257
}

Markus Faerevaag's avatar
Markus Faerevaag committed
258
259
260
wd_ls()
{
    wd_getdir $1
261
    ls ${dir/#\~/$HOME}
Markus Faerevaag's avatar
Markus Faerevaag committed
262
263
264
265
266
267
268
269
}

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

270
271
wd_show()
{
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
    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
287
        PWD="${PWD/$HOME/~}"
288
289
290
291
292
293
294
295
296
297
298
299
        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
300
            wd_print_msg $WD_YELLOW "No warp point to $(echo $PWD | sed "s:$HOME:~:")"
301
302
        fi
    fi
Markus Færevaag's avatar
Markus Færevaag committed
303
304
}

305
306
307
308
309
310
311
312
313
314
315
316
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]}
317

318
            if [ -d "${val/#\~/$HOME}" ]
319
320
321
322
323
324
325
326
327
328
            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
329
    then
330
        wd_print_msg $WD_BLUE "No warp points to clean, carry on!"
Markus Færevaag's avatar
Markus Færevaag committed
331
    else
332
333
334
335
336
337
338
        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
339
340
341
    fi
}

342
343
344
345
346
347
348
349
350
351
wd_export_static_named_directories() {
  if [[ -z $WD_SKIP_EXPORT ]]
  then
    grep '^[0-9a-zA-Z_-]\+:' "$WD_CONFIG" | sed -e "s,~,$HOME," -e 's/:/=/' | while read warpdir ; do
	    hash -d "$warpdir"
    done
  fi
}

local WD_CONFIG=${WD_CONFIG:-$HOME/.warprc}
352
353
354
local WD_QUIET=0
local WD_EXIT_CODE=0
local WD_DEBUG=0
355

356
357
358
# 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
359

360
361
362
363
364
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
365

366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
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
381
382
else
    wd_export_static_named_directories
383
384
385
386
387
388
389
390
fi

# load warp points
typeset -A points
while read -r line
do
    arr=(${(s,:,)line})
    key=${arr[1]}
391
392
    # join the rest, in case the path contains colons
    val=${(j,:,)arr[2,-1]}
393
394
395

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

# get opts
Markus Faerevaag's avatar
Markus Faerevaag committed
398
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
399

400
401
# 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
402
403
404
then
    wd_print_usage

405
406
    # check if config file is writeable
elif [ ! -w $WD_CONFIG ]
407
then
408
    # do nothing
409
    # can't run `exit`, as this would exit the executing shell
410
    wd_exit_fail "\'$WD_CONFIG\' is not writeable."
411
412

else
413
414

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

## garbage collection
# if not, next time warp will pick up variables from this run
# remember, there's no sub shell
478
479
480
481
482
483
484

unset wd_warp
unset wd_add
unset wd_remove
unset wd_show
unset wd_list_all
unset wd_print_msg
485
unset wd_yesorno
486
unset wd_print_usage
487
488
489
unset wd_alt_config
unset wd_quiet_mode
unset wd_print_version
490
491
unset wd_export_static_named_directories
unset wd_o
492

Markus Faerevaag's avatar
Markus Faerevaag committed
493
unset args
494
unset points
Markus Faerevaag's avatar
Markus Faerevaag committed
495
unset val &> /dev/null # fixes issue #1
496
497
498
499
500
501
502

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