|
@@ -2,21 +2,232 @@
|
2
|
2
|
|
3
|
3
|
shellfu import pretty
|
4
|
4
|
|
|
5
|
+#
|
|
6
|
+# Root of the cache.
|
|
7
|
+#
|
|
8
|
+# All cache objects are created or looked up under this directory. The path
|
|
9
|
+# is validated against common dangerous scenarios; see $CACHED__ROOT_VALID.
|
|
10
|
+#
|
|
11
|
+CACHED__ROOT=${CACHED__ROOT:-}
|
|
12
|
+
|
|
13
|
+#
|
|
14
|
+# Consider $CACHED__ROOT always valid
|
|
15
|
+#
|
|
16
|
+# To minimize chance of damage, value of $CACHED__ROOT is validated using
|
|
17
|
+# heuristic based on common caching paths such as /tmp, /var/cache or
|
|
18
|
+# $HOME/.cache.
|
|
19
|
+#
|
|
20
|
+# However, if you insist on using non-standard caching location, set this
|
|
21
|
+# to 'always' to turn off this validation.
|
|
22
|
+#
|
|
23
|
+# Note that this has no effect on cases when $CACHED__ROOT is empty,
|
|
24
|
+# unset or exactly '/'; in these cases validation will always fail.
|
|
25
|
+#
|
|
26
|
+CACHED__ROOT_VALID=${CACHED__ROOT_VALID:-}
|
|
27
|
+
|
5
|
28
|
|
6
|
29
|
cached() {
|
7
|
30
|
#
|
8
|
31
|
# Get cached or new output of command $@
|
9
|
32
|
#
|
10
|
33
|
# Usage:
|
11
|
|
- # cached [-m] [-e|-E] [-o|-O] [-d|-D] CMD [ARG]
|
|
34
|
+ #
|
|
35
|
+ # CACHED__ROOT=$HOME/.cache/myapp
|
|
36
|
+ # cached [-m] [-w] [-a ATTR] CMD [ARG]
|
12
|
37
|
#
|
13
|
38
|
# Look up CMD with any ARGs in local cache and return result on hit.
|
14
|
39
|
# In case of miss, run command to create the cache first.
|
15
|
40
|
#
|
16
|
|
- # Specify -m to enforce cache miss, ie. as if CMD is run for the first
|
17
|
|
- # time.
|
|
41
|
+ # Cache objects are identified by computing a MD5 hash from combination
|
|
42
|
+ # of several attributes. By default, only CMD and ARGs are included;
|
|
43
|
+ # that is, same CMD+ARGs combination may match, no matter what is current
|
|
44
|
+ # working directory. Optionally, you can specify -w if current directory
|
|
45
|
+ # should be included in the identifier. (This is useful for commands like
|
|
46
|
+ # 'ls'.) You can also add arbitrary string by providing ATTR parameter.
|
|
47
|
+ #
|
|
48
|
+ # For example, following set of commands would either hit or miss cache:
|
|
49
|
+ #
|
|
50
|
+ # CACHED__ROOT=$(mktemp -d)
|
|
51
|
+ # cached ls /etc # miss (first run)
|
|
52
|
+ # cached ls /etc # hit
|
|
53
|
+ # cached -w ls /etc # miss (first run with $PWD consideration)
|
|
54
|
+ # pushd /tmp
|
|
55
|
+ # cached -w ls /etc # miss (different $PWD)
|
|
56
|
+ # pushd /tmp
|
|
57
|
+ # cached -w ls /etc # hit (back to previous $PWD)
|
|
58
|
+ # cached -a foo ls /etc # miss (first run with 'foo')
|
|
59
|
+ # cached -a bar ls /etc # miss (first run with 'bar')
|
|
60
|
+ # cached -a foo ls /etc # hit (second run with 'foo')
|
|
61
|
+ #
|
|
62
|
+ # All cache objects are queried or created under directory specified by
|
|
63
|
+ # global variable $CACHED__ROOT, which must be specified beforehand.
|
|
64
|
+ #
|
|
65
|
+ # NOTE: Caching of commands that process standard input is not supported.
|
|
66
|
+ # (I.e. cached() will close standard input immediately.)
|
|
67
|
+ #
|
|
68
|
+ local Cache # local cache root
|
|
69
|
+ local Workdir="$PWD" # current workdir
|
|
70
|
+ local Command # command to run
|
|
71
|
+ local es=2 # exit status of this function
|
|
72
|
+ local Miss=false # force cache miss?
|
|
73
|
+ local ObjPath # cache object path
|
|
74
|
+ local MatchWD=false # does workdir matter?
|
|
75
|
+ local Attr # custom attribute
|
|
76
|
+ while true; do case $1 in
|
|
77
|
+ --) shift; break ;;
|
|
78
|
+ -a) Attr=$2; shift 2 || return 2 ;;
|
|
79
|
+ -w) MatchWD=true; shift ;;
|
|
80
|
+ -m) Miss=true; shift ;;
|
|
81
|
+ -*) warn "bad argument: $1"; return 2 ;;
|
|
82
|
+ *) break ;;
|
|
83
|
+ esac done
|
|
84
|
+ __cached__validroot || {
|
|
85
|
+ warn "cache root (\$CACHED__ROOT) is invalid: '$CACHED__ROOT'"
|
|
86
|
+ return 2
|
|
87
|
+ }
|
|
88
|
+ Cache=$CACHED__ROOT
|
|
89
|
+ Command=$(printf '%q ' "$@")
|
|
90
|
+ Command=${Command% }
|
|
91
|
+ bash -n <<<"$Command" || {
|
|
92
|
+ warn "command is not a valid Bash command: $Command"
|
|
93
|
+ return 2
|
|
94
|
+ }
|
|
95
|
+ ObjPath=$(__cached__objpath)
|
|
96
|
+ debug -v Command ObjPath Attr Miss MatchWD
|
|
97
|
+ exec 0<&-
|
|
98
|
+ if $Miss; then
|
|
99
|
+ debug FORCED_MISS
|
|
100
|
+ __cached__run; es=$?
|
|
101
|
+ elif __cached__hit; then
|
|
102
|
+ debug HIT
|
|
103
|
+ else
|
|
104
|
+ debug MISS
|
|
105
|
+ __cached__run; es=$?
|
|
106
|
+ fi
|
|
107
|
+ __cached__pull
|
|
108
|
+ return $es
|
|
109
|
+}
|
|
110
|
+
|
|
111
|
+cached__kill() {
|
|
112
|
+ #
|
|
113
|
+ # Kill whole cache
|
|
114
|
+ #
|
|
115
|
+ __cached__validroot || {
|
|
116
|
+ warn "cache root (\$CACHED__ROOT) is invalid: '$CACHED__ROOT'"
|
|
117
|
+ return 2
|
|
118
|
+ }
|
|
119
|
+ rm -rf "$CACHED__ROOT"
|
|
120
|
+}
|
|
121
|
+
|
|
122
|
+cached__prune() {
|
|
123
|
+ #
|
|
124
|
+ # Remove items older than age $1
|
|
125
|
+ #
|
|
126
|
+ # Age must be in format:
|
|
127
|
+ #
|
|
128
|
+ # N[d]
|
|
129
|
+ #
|
|
130
|
+ # where N is an integer meaning age in minutes, unless suffix 'd' is
|
|
131
|
+ # added, in which case N means age in days (ie. N * 24 hours).
|
|
132
|
+ #
|
|
133
|
+ # Examples:
|
|
134
|
+ #
|
|
135
|
+ # cached__prune 15 # remove items older than 15 minutes
|
|
136
|
+ # cached__prune 5d # remove items older than 24*5 hours
|
|
137
|
+ #
|
|
138
|
+ local age=$1
|
|
139
|
+ local item
|
|
140
|
+ local scancmd
|
|
141
|
+ __cached__validroot || {
|
|
142
|
+ warn "cache root (\$CACHED__ROOT) is invalid: '$CACHED__ROOT'"
|
|
143
|
+ return 2
|
|
144
|
+ }
|
|
145
|
+ scancmd="find $CACHED__ROOT/cached -mindepth 1 -maxdepth 1"
|
|
146
|
+ case $age in
|
|
147
|
+ *d) scancmd+=" -mtime +${age%d}" ;;
|
|
148
|
+ *) scancmd+=" -mmin +$age" ;;
|
|
149
|
+ esac
|
|
150
|
+ for item in $(eval "$scancmd"); do
|
|
151
|
+ rm -r "$item"
|
|
152
|
+ done
|
|
153
|
+}
|
|
154
|
+
|
|
155
|
+__cached__describe() {
|
|
156
|
+ #
|
|
157
|
+ # Create command call description
|
|
158
|
+ #
|
|
159
|
+ echo "Command=$Command"
|
|
160
|
+ $MatchWD && echo "Workdir=$(readlink -m "$Workdir")"
|
|
161
|
+ test -n "$Attr" && echo "Attr=$Attr"
|
|
162
|
+}
|
|
163
|
+
|
|
164
|
+__cached__hit() {
|
|
165
|
+ #
|
|
166
|
+ # True if $Command has cache hit
|
|
167
|
+ #
|
|
168
|
+ $Miss && return 1
|
|
169
|
+ test -d "$ObjPath"
|
|
170
|
+}
|
|
171
|
+
|
|
172
|
+__cached__objid() {
|
|
173
|
+ #
|
|
174
|
+ # Describe command $Command called from directory $Workdir
|
|
175
|
+ #
|
|
176
|
+ __cached__describe | md5sum | cut -d\ -f1
|
|
177
|
+}
|
|
178
|
+
|
|
179
|
+__cached__objpath() {
|
|
180
|
+ #
|
|
181
|
+ # Print cache object path
|
|
182
|
+ #
|
|
183
|
+ printf %s "$Cache/cached/$(__cached__objid)"
|
|
184
|
+}
|
|
185
|
+
|
|
186
|
+__cached__pull() {
|
|
187
|
+ #
|
|
188
|
+ # Pull result from cache object
|
|
189
|
+ #
|
|
190
|
+ cat "$ObjPath/out"
|
|
191
|
+ cat "$ObjPath/err" >&2
|
|
192
|
+ return "$(<"$ObjPath/es")"
|
|
193
|
+}
|
|
194
|
+
|
|
195
|
+__cached__run() {
|
|
196
|
+ #
|
|
197
|
+ # Run command, creating cache object
|
|
198
|
+ #
|
|
199
|
+ local es # command exit status
|
|
200
|
+ rm -rf "$ObjPath"
|
|
201
|
+ mkdir -p "$ObjPath"
|
|
202
|
+ __cached__describe >"$ObjPath/desc"
|
|
203
|
+ eval "$Command" \
|
|
204
|
+ >"$ObjPath/out"\
|
|
205
|
+ 2>"$ObjPath/err"; es=$?
|
|
206
|
+ echo $es>"$ObjPath/es"
|
|
207
|
+ return $es
|
|
208
|
+}
|
|
209
|
+
|
|
210
|
+__cached__validroot() {
|
|
211
|
+ #
|
|
212
|
+ # True if $CACHED__ROOT is valid
|
|
213
|
+ #
|
|
214
|
+ test -n "$CACHED__ROOT" || return 1
|
|
215
|
+ test "$CACHED__ROOT" == / && return 1
|
|
216
|
+ test "$CACHED__ROOT_VALID" == 'always' && return 0
|
|
217
|
+ case $CACHED__ROOT in
|
|
218
|
+ /var/cache/[[:word:].-]*) return 0 ;;
|
|
219
|
+ /tmp/[[:word:].-]*) return 0 ;;
|
|
220
|
+ /var/tmp/[[:word:].-]*) return 0 ;;
|
|
221
|
+ $HOME/.cache/[[:word:].-]*) return 0 ;;
|
|
222
|
+ esac
|
|
223
|
+ return 1
|
|
224
|
+}
|
|
225
|
+
|
|
226
|
+__cached__validcmd() {
|
|
227
|
+ #
|
|
228
|
+ # True if $Command is a valid Bash command
|
18
|
229
|
#
|
19
|
|
- :
|
|
230
|
+ bash -n <<<"$Command"
|
20
|
231
|
}
|
21
|
232
|
|
22
|
233
|
#shellfu module-version=__MKIT_PROJ_VERSION__
|