aboutsummaryrefslogtreecommitdiff
path: root/ksh/kshrc.d/keep.ksh
blob: 0451fa68f31cd88a912e96a097af4116e2ec8e90 (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
149
150
151
152
153
154
155
156
157
158
# Limit to ksh93; most of this works in mksh, but not all of it, and pdksh
# doesn't have a `typeset -p` that includes printable values at all.
case $KSH_VERSION in
    *' 93'*) ;;
    *) return ;;
esac

#
# keep -- Main function for kshkeep; provided with a list of NAMEs, whether
# shell functions or variables, writes the current definition of each NAME to a
# directory $KSHKEEP (defaults to ~/.kshkeep.d) with a .ksh 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
#
function keep {

    # Name self
    typeset self
    self=keep

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

    # Parse options
    typeset opt delete
    typeset 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
$self: Keep variables and functions in shell permanently by writing them to
named scripts iterated on shell start, in \$KSHKEEP (defaults to
~/.kshkeep.d).

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

EOF
                return
                ;;

            # Unknown other option
            \?)
                printf 'ksh: %s -%s: invalid option\n' \
                    "$self" "$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
        typeset -i errors
        errors=0

        # Iterate through the NAMEs given
        typeset 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 'ksh: %s: %s not a valid NAME\n' \
                        "$self" "$name" >&2
                    ((errors++))
                    ;;

                # NAME is valid, proceed
                *)

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

                    # Save a function
                    elif [[ $(whence -v "$name" 2>/dev/null) == *' is a function' ]] ; then
                        typeset -f -- "$name" >"$kshkeep"/"$name".ksh ||
                            ((errors++))

                    # Save a variable
                    elif [[ -n "$name" ]] ; then
                        typeset -p -- "$name" >"$kshkeep"/"$name".ksh ||
                            ((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 'ksh: %s: must specify at least one NAME to delete\n' \
            "$self" >&2
        return 2
    fi

    # Otherwise the user must want us to print all the NAMEs kept
    (
        for keep in "$kshkeep"/*.ksh ; do
            [[ -f "$keep" ]] || break
            keep=${keep##*/}
            keep=${keep%.ksh}
            printf '%s\n' "$keep"
        done
    )
}

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