|  | @@ -33,10 +33,11 @@ cached() {
 | 
	
		
			
			| 33 | 33 |      # Usage:
 | 
	
		
			
			| 34 | 34 |      #
 | 
	
		
			
			| 35 | 35 |      #     CACHED__ROOT=$HOME/.cache/myapp
 | 
	
		
			
			| 36 |  | -    #     cached [-m] [-w] [-a ATTR] CMD [ARG]
 | 
	
		
			
			|  | 36 | +    #     cached [-m] [-w] [-a ATTR] [-e ES_EXPR] CMD [ARG]
 | 
	
		
			
			| 37 | 37 |      #
 | 
	
		
			
			| 38 | 38 |      # Look up CMD with any ARGs in local cache and return result on hit.
 | 
	
		
			
			| 39 |  | -    # In case of miss, run command to create the cache first.
 | 
	
		
			
			|  | 39 | +    # In case of miss, run command and if exit status is zero (can be changed
 | 
	
		
			
			|  | 40 | +    # by -e argument; see below), create the cache and return it.
 | 
	
		
			
			| 40 | 41 |      #
 | 
	
		
			
			| 41 | 42 |      # Cache objects are identified by computing a MD5 hash from combination
 | 
	
		
			
			| 42 | 43 |      # of several attributes.  By default, only CMD and ARGs are included;
 | 
	
	
		
			
			|  | @@ -62,6 +63,19 @@ cached() {
 | 
	
		
			
			| 62 | 63 |      # All cache objects are queried or created under directory specified by
 | 
	
		
			
			| 63 | 64 |      # global variable $CACHED__ROOT, which must be specified beforehand.
 | 
	
		
			
			| 64 | 65 |      #
 | 
	
		
			
			|  | 66 | +    # Exit status has to match ES_EXPR in order to be matched.  ES_EXPR has
 | 
	
		
			
			|  | 67 | +    # form of comma-separated list of exit statuses or simple exit status
 | 
	
		
			
			|  | 68 | +    # ranges. Valid ranges are: `N` which matches exactly `N` or `M-N`,
 | 
	
		
			
			|  | 69 | +    # which matches any status from `M` to `N` inclusively.
 | 
	
		
			
			|  | 70 | +    #
 | 
	
		
			
			|  | 71 | +    # For example, following commands do or do not create cache (assuming
 | 
	
		
			
			|  | 72 | +    # no cache hit):
 | 
	
		
			
			|  | 73 | +    #
 | 
	
		
			
			|  | 74 | +    #     cached false              # no: by default ES_EXPR is only 0
 | 
	
		
			
			|  | 75 | +    #     cached -e 0,1 false       # yes: 1 matches `0,1`
 | 
	
		
			
			|  | 76 | +    #     cached -e 0-3,9 exit_4    # no: 4 does not match `0-3,9`
 | 
	
		
			
			|  | 77 | +    #     cached -e 0-3,9 exit_3    # yes: 3 matches `0-3,9`
 | 
	
		
			
			|  | 78 | +    #
 | 
	
		
			
			| 65 | 79 |      # NOTE: Caching of commands that process standard input is not supported.
 | 
	
		
			
			| 66 | 80 |      # (I.e. cached() will close standard input immediately.)
 | 
	
		
			
			| 67 | 81 |      #
 | 
	
	
		
			
			|  | @@ -73,9 +87,11 @@ cached() {
 | 
	
		
			
			| 73 | 87 |      local ObjPath           # cache object path
 | 
	
		
			
			| 74 | 88 |      local MatchWD=false     # does workdir matter?
 | 
	
		
			
			| 75 | 89 |      local Attr              # custom attribute
 | 
	
		
			
			|  | 90 | +    local EsExpr=0          # allowed exit status
 | 
	
		
			
			| 76 | 91 |      while true; do case $1 in
 | 
	
		
			
			| 77 | 92 |          --)     shift; break ;;
 | 
	
		
			
			| 78 | 93 |          -a)     Attr=$2; shift 2 || return 2 ;;
 | 
	
		
			
			|  | 94 | +        -e)     EsExpr=$2; shift 2 || return 2 ;;
 | 
	
		
			
			| 79 | 95 |          -w)     MatchWD=true; shift ;;
 | 
	
		
			
			| 80 | 96 |          -m)     Miss=true; shift ;;
 | 
	
		
			
			| 81 | 97 |          -*)     warn "bad argument: $1"; return 2 ;;
 | 
	
	
		
			
			|  | @@ -161,6 +177,20 @@ __cached__describe() {
 | 
	
		
			
			| 161 | 177 |      test -n "$Attr" && echo "Attr=$Attr"
 | 
	
		
			
			| 162 | 178 |  }
 | 
	
		
			
			| 163 | 179 |  
 | 
	
		
			
			|  | 180 | +__cached__es_match() {
 | 
	
		
			
			|  | 181 | +    #
 | 
	
		
			
			|  | 182 | +    # True if exit status $1 matches expression $EsExpr
 | 
	
		
			
			|  | 183 | +    #
 | 
	
		
			
			|  | 184 | +    local es=$1
 | 
	
		
			
			|  | 185 | +    local part
 | 
	
		
			
			|  | 186 | +    for part in ${EsExpr//,/ }; do
 | 
	
		
			
			|  | 187 | +        test -n "$part" || continue
 | 
	
		
			
			|  | 188 | +        #FIXME: a rather funny implementation (works, though...)
 | 
	
		
			
			|  | 189 | +        eval "echo {${part/-/..}}" | grep -qwF "$es" && return 0
 | 
	
		
			
			|  | 190 | +    done
 | 
	
		
			
			|  | 191 | +    return 1
 | 
	
		
			
			|  | 192 | +}
 | 
	
		
			
			|  | 193 | +
 | 
	
		
			
			| 164 | 194 |  __cached__hit() {
 | 
	
		
			
			| 165 | 195 |      #
 | 
	
		
			
			| 166 | 196 |      # True if $Command has cache hit
 | 
	
	
		
			
			|  | @@ -194,16 +224,28 @@ __cached__pull() {
 | 
	
		
			
			| 194 | 224 |  
 | 
	
		
			
			| 195 | 225 |  __cached__run() {
 | 
	
		
			
			| 196 | 226 |      #
 | 
	
		
			
			| 197 |  | -    # Run command, creating cache object
 | 
	
		
			
			|  | 227 | +    # Run command, creating cache object if exit status matches $EsExpr
 | 
	
		
			
			| 198 | 228 |      #
 | 
	
		
			
			| 199 | 229 |      local es        # command exit status
 | 
	
		
			
			| 200 | 230 |      rm -rf "$ObjPath"
 | 
	
		
			
			| 201 | 231 |      mkdir -p "$ObjPath"
 | 
	
		
			
			| 202 | 232 |      __cached__describe >"$ObjPath/desc"
 | 
	
		
			
			| 203 | 233 |      eval "$Command" \
 | 
	
		
			
			| 204 |  | -        >"$ObjPath/out"\
 | 
	
		
			
			| 205 |  | -        2>"$ObjPath/err"; es=$?
 | 
	
		
			
			| 206 |  | -    echo $es>"$ObjPath/es"
 | 
	
		
			
			|  | 234 | +        >"$ObjPath/out.tmp"\
 | 
	
		
			
			|  | 235 | +        2>"$ObjPath/err.tmp"; es=$?
 | 
	
		
			
			|  | 236 | +    echo $es>"$ObjPath/es.tmp"
 | 
	
		
			
			|  | 237 | +    if __cached__es_match $es; then
 | 
	
		
			
			|  | 238 | +        mv "$ObjPath/out.tmp" "$ObjPath/out"
 | 
	
		
			
			|  | 239 | +        mv "$ObjPath/err.tmp" "$ObjPath/err"
 | 
	
		
			
			|  | 240 | +        mv "$ObjPath/es.tmp" "$ObjPath/es"
 | 
	
		
			
			|  | 241 | +    else
 | 
	
		
			
			|  | 242 | +        warn "bad exit status; skipping cache creation: $es does not match $EsExpr"
 | 
	
		
			
			|  | 243 | +        cat "$ObjPath/out.tmp"
 | 
	
		
			
			|  | 244 | +        cat "$ObjPath/err.tmp" >&2
 | 
	
		
			
			|  | 245 | +        rm "$ObjPath/out.tmp"
 | 
	
		
			
			|  | 246 | +        rm "$ObjPath/err.tmp"
 | 
	
		
			
			|  | 247 | +        rm "$ObjPath/es.tmp"
 | 
	
		
			
			|  | 248 | +    fi
 | 
	
		
			
			| 207 | 249 |      return $es
 | 
	
		
			
			| 208 | 250 |  }
 | 
	
		
			
			| 209 | 251 |  
 |