#!/bin/bash ######################################################################## # yruba -- whY RUles for BAsh? # ... because it's cool! # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation # Foundation, Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. # # (C) 2005, 2006 Harald Kirsch ######################################################################## set -e # stop on any error yruba_me="${0##*/}" yruba_version=2.9 ######################################################################## # Functions that are helpful in built tasks ######################################################################## ######################################################################## # old t1 [...] -d [d1 [...]] # # returns true if any of the file targets t1 [...] is older than (test # -ot) any of the dependency files listed after -d. # # Option -d must be present. If no dependency is given, false is # returned, meaning the target(s) are not old, i.e. up-to-date. If any # of the dependencies does not exist as a file (test -f) yruba exits # with an error message. If any of the targets does not exist as a # file, true is returned (this is a feature of 'test -ot'). # old() { local x local tlist="" while [ "$#" -gt 0 ]; do x="$1"; shift if [ "$x" = "-d" ]; then break; fi tlist="$tlist '${x//\'/\'\\\'\'}'" done if [ "$x" != "-d" ]; then die ${FUNCNAME}: called for "'"$(eval echo "$tlist")"'" \ but required option -d is missing fi ## Due to the double loop, we would need two "$@", one for the ## target list and one for the dependency list. I try to simulate ## this here, but it is a bit of a hack, because it relies on the ## fact that once the loop is started, "$@" can safely be changed ## without influencing the loop. local target local d local dlist=$(lcreate "$@") local first=true eval set -- "$tlist" for target in "$@"; do $first && eval set -- "$dlist" first=false for d in "$@"; do test -e "$d" || die ${FUNCNAME}: dependency "'$d'" does not exist if [ "$target" -ot "$d" ]; then dlog " '$target'" older than "'$d'" return 0 fi done done return 1 } haveClass() { local class="$1" local c="x$$" local f=/tmp/$c.java echo "class $c { $class y; }" >$f if ${JAVAC:-javac} -d /tmp $f 1>/dev/null 2>/dev/null; then local r=0 else local r=1; fi rm -f $f /tmp/$c.class return $r } #map filenames to other directory and change extension mapFilenames() { local headlen="${#1}" local newhead="$2" local newext="$3" shift 3 local result="" local fname="" if [ . = "$newext" ]; then doext=false newext='' else doext=true test -z "$newext" || newext=".$newext" fi for fname; do eval fname='${fname:'"$headlen"'}' $doext && fname="${fname%.*}" fname="${newhead}${fname}${newext}" #use of lappend would be cleaner, but its deadly slow fname=${fname//\'/\'\\\'\'} result="$result '$fname'" done echo "$result" } die() { echo 1>&2 ${yruba_me}: "$@" exit 1 } dlog() { $yruba_debug || return 0 local n="" if [ "$1" = "-n" ]; then n=-n; shift; fi echo $n "${yruba_indent}$*" } mapTarget() { local pattern="$1" local ttag="$2" yruba_mapped=$(yruba_put "$yruba_mapped" "$ttag" "$pattern") yruba_tagmap="$pattern) yruba_ttag=\"$ttag\" ;; ${yruba_tagmap}" } ######################################################################## # # A set of functions to simulate an associative array. The associative # array is stored as a string in any variable. Both, key and value are # encoded not to contain single vertical bars. Instead vertical bars # are encoded as "|." A key/value pair is encoded like "||key|=value" # and in addition, the whole string is terminated by "||". # yruba_encode() { local p=${1//|/|.} echo "$p" } yruba_decode() { local p=${1//|./|} echo "$p" } ##### # usage: yruba_remove map key # removes from the given map the entry for key and returns the result yruba_remove() { local table=$1 local key=$(yruba_encode "$2") local head=${table%||$key|=*} if [ "$head" = "$table" ]; then echo "$table"; return; fi local tail="${table#*||$key|=*||}" echo "${head}||${tail}" } ##### # usage: yruba_get mapname key [default] yruba_get() { # usage: yruba_get mapname local table=${1} local key=$(yruba_encode "$2") local dflt=$3 local tail="${table#*||$key|=}" if [ "$table" = "$tail" ]; then echo "$dflt"; return; fi local value=$(yruba_decode "${tail%%||*}") echo "$value" } ##### # usage: yruba_put map key value # enters the key/value pair into the map represented by "$1" and # returns the resulting map representation. yruba_put() { local map=$1 if [ -z "$map" ]; then map='||'; else map=$(yruba_remove "$map" "$2") fi local key=$(yruba_encode "$2") local value=$(yruba_encode "$3") echo "||$key|=${value}$map" } ##### # usage: mapname # Returns a list in the sense of lcreate, lget, etc. that contains # all the keys in the given map. # Example: keys=$(yruba_keys "$map") yruba_keys() { test -z "$table" && table="||" local table=$1 local result="" local l while [ "$table" != "||" ]; do local key=$(yruba_decode "${table%%|=*}") result=$(lappend "$result" "${key:2}") table="||${table#||*||}" done echo "$result" } ######################################################################## # Simple list functions. # A list is maintained as a single string that contains proper quoting # such that for a list l the command # eval set "$l" # results in the positional parameters to be set to the list elements. # Creates a list from all its arguments and returns it. # list=$(lcreate elem1 [...]) lcreate() { local result="" local x tmp local sep="" for x in "$@"; do tmp=${x//\'/\'\\\'\'} s="${s}${sep}'$tmp'" sep=" " done echo "$s" } # Sticks an element in front of a list and returns the list. # list=$(lpush elem "$list") lpush() { local top=${1//\'/\'\\\'\'} (test -z "$2" && echo "'$top'") || echo "'$top' $2" } # Appends an element to the end of a list and returns the list. # list=$(lappend "$list" elem) lappend() { local ele=${2//\'/\'\\\'\'} (test -z "$1" && echo "'$ele'") || echo "$1 '$ele'" } # Returns the first element of the list # top=$(lhead "$list") lhead() { eval set -- "$1" if [ "$#" = 0 ]; then echo 1>&2 lhead: list empty return 1 fi echo "$1" } # Returns the list after removing the first element. # list=$(ltail "$list") ltail() { eval set -- "$1" if [ "$#" = 0 ]; then echo 1>&2 ltail: list empty return 1 fi shift lcreate "$@" } # Returns the i'th element of a list, where i counts from zero. If # the index is to large, the empty string is returned. # value=$(lget "$list" 3) lget() { local i=$(($2+1)) eval set -- "$1" if [ "$#" = 0 ]; then echo 1>&2 lget: list empty return 1 fi if [ $i -le 0 -o $i -gt "$#" ]; then echo 1>&2 lget: index "$((i-1))" out of range 0.."$(($#-1))" return 1 fi echo "${!i}" } # Returns the number of elements in the list. llength() { eval set -- "$1" echo "$#" } ######################################################################## # store one-line documentation for targets ydoc() { local target=$1; shift local text=$* YRUBA_DOCS=$(yruba_put "$YRUBA_DOCS" "$target" "$text") } ######################################################################## yruba_savecmd() { local yruba_here=$(pwd) "$@" local retval=$? cd "$yruba_here" return "$retval" } ######################################################################## yruba_makeTarget() { local yruba_t="$1" ## map the target through pattern matching to a target tag that is ## then used to find dependencies, test and command for this target local yruba_ttag eval "case \"$1\" in $yruba_tagmap *) yruba_ttag=\"$1\" ;; esac" # did we work on this target before already, then just return if [ $(yruba_get "$yruba_done" "$yruba_t" 0) -eq 1 ]; then dlog have seen "'$yruba_t'" already return 0 fi dlog making target "'$yruba_t'" # is t being made currently? That would be a bug test $(yruba_get "$yruba_active" "$yruba_t" 0) -eq 1 \ && die "target '$yruba_t' has dependency loop: ${yruba_stack}" yruba_active=$(yruba_put "$yruba_active" "$yruba_t" 1) yruba_stack=$(lpush "$yruba_t" "$yruba_stack") if $yruba_sdeps; then echo "${yruba_indent}$yruba_t"; fi # make all dependencies for this target local yruba_cmd="dep_$yruba_ttag" local yruba_deps="" eval set -- # "$@" will be the dependency list if type -p "$yruba_cmd" >/dev/null; then yruba_deps=$("$yruba_cmd" "$yruba_t") eval set -- "$yruba_deps" yruba_make "$yruba_t" "$@" fi if ! $yruba_sdeps; then # prepare to run the test for this target, if there is one, # otherwise default to true yruba_cmd="test_$yruba_ttag" if type -p "$yruba_cmd" >/dev/null; then dlog running "'$yruba_cmd' on '$yruba_t'" $yruba_deps else yruba_cmd=true fi # run the test and eventually the command if yruba_savecmd "$yruba_cmd" "$yruba_t" "$@"; then yruba_cmd="cmd_$yruba_ttag" if type -p "$yruba_cmd" >/dev/null; then $yruba_debug || $yruba_quiet || echo "+ $yruba_t" dlog executing: $(lcreate "$yruba_cmd" "$yruba_t" "$@") yruba_savecmd "$yruba_cmd" "$yruba_t" "$@" elif ! test -e "$yruba_t"; then die no command to make "'$yruba_t'" and file does not exist fi else $yruba_debug || $yruba_quiet || echo "- $yruba_t" dlog no need to run: "'cmd_$yruba_ttag' '$yruba_t'" fi fi # clean up the stack, this target is finished completely yruba_stack=$(ltail "$yruba_stack") yruba_active=$(yruba_remove "$yruba_active" "$yruba_t") yruba_done=$(yruba_put "$yruba_done" "$yruba_t" 1) #echo '------------------------------------' #set | egrep '^[a-z_]+=' } ######################################################################## yruba_make() { local -r yruba_target="$1" shift if [ "$#" -eq 0 ]; then return; fi dlog "considering dependencies of '$yruba_target':" $(lcreate "$@") yruba_indent=" $yruba_indent" local yruba_t for yruba_t in "$@"; do yruba_makeTarget "$yruba_t" done yruba_indent="${yruba_indent:2}" } ######################################################################## yruba_usage() { cat <&2 "$yruba_me: '$x' documented but 'cmd_$x' does not exist" continue fi if ! $yruba_info; then continue; fi ## if the target is mapped from a pattern, we rather print the ## pattern than the target in the info string y=$(yruba_get "$yruba_mapped" "$x") if ! [ -z "$y" ]; then t="$y"; else t="$x"; fi ## fetch the doc into a variable first because the function call ## in quotes as param of echo is nasty syntax y=$(yruba_get "$YRUBA_DOCS" "$x") echo "$t" -- "$y" done unset t x if $yruba_info; then exit 0; fi if [ -z "$yruba_targets" ]; then if [ -z "$defaultTarget" ]; then die no target given; fi yruba_targets=$(lcreate "$defaultTarget") fi eval set -- "$yruba_targets" yruba_make '#top' "$@"