aboutsummaryrefslogtreecommitdiff
path: root/bash/bashrc.d/keep.bash
blob: 1b2fd9b7f40fe7f64fa8d1e7669b368a042e9e32 (plain)
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#
# Main function for bashkeep; provided with a list of NAMEs, whether shell
# functions or variables, writes the current definition of each NAME to a
# directory $BASHKEEP (defaults to ~/.bashkeep.d) with a .bash suffix, each
# of which is reloaded each time this file is called. This allows you to
# quickly arrange to keep that useful shell function or variable you made
# inline on subsequent logins.
#
keep() {

    # Figure out the directory to which we're reading and writing these scripts
    local bashkeep
    bashkeep=${BASHKEEP:-$HOME/.bashkeep.d}
    mkdir -p -- "$bashkeep" || return

    # Parse options
    local opt delete
    local OPTERR OPTIND OPTARG
    while getopts 'dh' opt ; do
        case $opt in

            # -d given; means delete the keepfiles for the given names
            d)
                delete=1
                ;;

            # -h given; means show help
            h)
                cat <<EOF
$FUNCNAME: Keep variables and functions in shell permanently by writing them to
named scripts iterated on shell start, in \$BASHKEEP (defaults to
~/.bashkeep.d).

USAGE:
  $FUNCNAME
    List all the current kept variables and functions
  $FUNCNAME NAME1 [NAME2 ...]
    Write the current definition for the given NAMEs to keep files
  $FUNCNAME -d NAME1 [NAME2 ...]
    Delete the keep files for the given NAMEs
  $FUNCNAME -h
    Show this help

EOF
                return
                ;;

            # Unknown other option
            \?)
                printf 'bash: %s -%s: invalid option\n' \
                    "$FUNCNAME" "$opt" >&2
                return 2
                ;;
        esac
    done
    shift "$((OPTIND-1))"

    # If any arguments left, we must be either keeping or deleting
    if (($#)) ; then

        # Start keeping count of any errors
        local -i errors
        errors=0

        # Iterate through the NAMEs given
        local name
        for name in "$@" ; do

            # Check NAMEs for validity
            case $name in

                # NAME must start with letters or an underscore, and contain no
                # characters besides letters, numbers, or underscores
                *[^a-zA-Z0-9_]*|[^a-zA-Z_]*)
                    printf 'bash: %s: %s not a valid NAME\n' \
                        "$FUNCNAME" "$name" >&2
                    ((errors++))
                    ;;

                # NAME is valid, proceed
                *)

                    # If -d was given, delete the keep files for the NAME
                    if ((delete)) ; then
                        rm -- "$bashkeep"/"$name".bash \
                            || ((errors++))

                    # Otherwise, attempt to create the keep file, using an
                    # appropriate call to the declare builtin
                    else
                        { case $(type -t "$name") in
                            'function')
                                declare -f -- "$name"
                                ;;
                            *)
                                declare -p -- "$name"
                                ;;
                        esac ; } > "$bashkeep"/"$name".bash \
                            || ((errors++))
                    fi
                    ;;
            esac
        done

        # Return 1 if we accrued any errors, 0 otherwise
        return "$((errors > 0))"
    fi

    # Deleting is an error, since we need at least one argument
    if ((delete)) ; then
        printf 'bash: %s: must specify at least one NAME to delete\n'
            "$FUNCNAME" >&2
        return 2
    fi

    # Otherwise the user must want us to print all the NAMEs kept
    (
        shopt -s dotglob nullglob
        declare -a keeps
        keeps=("${bashkeep}"/*.bash)
        keeps=("${keeps[@]##*/}")
        keeps=("${keeps[@]%.bash}")
        ((${#keeps[@]})) || exit 0
        printf '%s\n' "${keeps[@]}"
    )
}

# Complete calls to keep with existing function names and variable names
complete -A function -A variable keep

# Load any existing scripts in bashkeep
if [[ -d ${BASHKEEP:-$HOME/.bashkeep.d} ]] ; then
    for bashkeep in "${BASHKEEP:-$HOME/.bashkeep.d}"/*.bash ; do
        [[ -e $bashkeep ]] || continue
        source "$bashkeep"
    done
    unset -v bashkeep
fi