aboutsummaryrefslogtreecommitdiff
path: root/bash/bashrc.d/keep.bash
blob: a8c3456af8ee3016c0e8a380b9fc4a4d029a86d3 (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
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
140
141
142
143
144
145
146
147
148
#
# keep -- 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.
#
# Consider a shell function declared inline with the NAME 'ayy':
#
#   $ ayy() { printf '%s\n' lmao ; }
#   $ ayy
#   lmao
#   $ keep ayy
#   $ keep
#   ayy
#   $ exit
#
# Then, on next login, the function is redefined for you:
#
#   $ ayy
#   lmao
#
# To get rid of it:
#
#   $ keep -d ayy
#
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[0]}: 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[0]}
    List all the current kept variables and functions
  ${FUNCNAME[0]} NAME1 [NAME2 ...]
    Write the current definition for the given NAMEs to keep files
  ${FUNCNAME[0]} -d NAME1 [NAME2 ...]
    Delete the keep files for the given NAMEs
  ${FUNCNAME[0]} -h
    Show this help

EOF
                return
                ;;

            # Unknown other option
            \?)
                printf 'bash: %s -%s: invalid option\n' \
                    "${FUNCNAME[0]}" "$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 ; 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[0]}" "$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++))

                    # Save a function
                    elif [[ $(type -t "$name") = 'function' ]] ; then
                        declare -f -- "$name" >"$bashkeep"/"$name".bash ||
                            ((errors++))

                    # Save a variable
                    elif declare -p -- "$name" >/dev/null ; then
                        declare -p -- "$name" >"$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[0]}" >&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[@]}"
    )
}

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