aboutsummaryrefslogtreecommitdiff
path: root/bash/bashrc.d/bd.bash
blob: 5d26e2b994be8d789f23bf4af378dd48544f4dd6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# Move back up the directory tree to the first directory matching the name
bd() {

    # For completeness' sake, we'll pass any options to cd
    local arg
    local -a opts
    for arg ; do
        case $arg in
            --)
                shift
                break
                ;;
            -*)
                shift
                opts=("${opts[@]}" "$arg")
                ;;
            *)
                break
                ;;
        esac
    done

    # We should have zero or one arguments after all that, bail if there are
    # more
    if (($# > 1)) ; then
        printf 'bash: %s: usage: %s [PATH]\n' \
            "$FUNCNAME" "$FUNCNAME" >&2
        return 2
    fi

    # The requested pattern is the first argument; strip trailing slashes if
    # there are any
    local req=$1
    [[ $req != / ]] || req=${req%/}

    # What to do now depends on the request
    local dir
    case $req in

        # If no argument at all, just go up one level
        '')
            dir=..
            ;;

        # Just go straight to the root or dot directories if asked
        /|.|..)
            dir=$req
            ;;

        # Anything else with a leading / needs to anchor to the start of the
        # path
        /*)
            dir=$req
            if [[ $PWD != "$dir"/* ]] ; then
                printf 'bash: %s: Directory name not in path\n' \
                    "$FUNCNAME" >&2
                return 1
            fi
            ;;

        # In all other cases, iterate through the directory tree to find a
        # match, or whittle the dir down to an empty string trying
        *)
            dir=${PWD%/*}
            while [[ -n $dir && $dir != */"$req" ]] ; do
                dir=${dir%/*}
            done
            if [[ -z $dir ]] ; then
                printf 'bash: %s: Directory name not in path\n' \
                    "$FUNCNAME" >&2
                return 1
            fi
            ;;
    esac

    # Try to change into the determined directory
    builtin cd "${opts[@]}" -- "$dir"
}

# Completion setup for bd
_bd() {
    local word
    word=${COMP_WORDS[COMP_CWORD]}

    # Build a list of dirs in $PWD
    local -a dirs
    while read -d / -r dir ; do
        if [[ -n $dir ]] ; then
            dirs=("${dirs[@]}" "$dir")
        fi
    done < <(printf %s "$PWD")

    # Complete with matching dirs
    COMPREPLY=( $(compgen -W "${dirs[*]}" -- "$word") )
}
complete -F _bd bd