#! /bin/sh # # Usage: $0 [-n] [-x] [-w world-account] [conf args] currency rate # # At least one of -n and -x must be given. # # -n means do non-x entries; -x means do x entries. # -w world-account means do transaction entries to/from the account # "world-account", where the given account is treated as though it were # the blank "world" pseudo-account -- i.e. exchange transactions to or # from this account are added. If this is given, *only* such entries # are processed (with or without x flag as per -n/-x). # [conf args] is zero or more arguments as for conf; each must be a # flag with a -, plus one following argument. These may be intermixed # with -n and/or -x and/or -w. # # Set PATH to find the accounting utilities PATH=/anne/accounting/bin:"$PATH" # do gets "n" and/or "x" to indicate which entries (flagged x or not) # to process do="" # confargs collects arguments to pass to the conf program confargs="" # three-letter currency specifier curr="" # number that the foreign currency should be multiplied by # to result in default currency (usually CAD *cents*) rate="" # "world" account, by default the blank string, but set to another # value if -w given. worldacc="" # Parse arguments # # This line is an "efficient" way to loop while $# is nonzero. while case $# in 0) false;; *) :;; esac do case "$1" in -n) do=n$do; shift;; -x) do=x$do; shift;; -w) case $# in 1) echo 'Missing argument to '"-w" 1>&2 exit 1 ;; *) worldacc="$2" shift; shift ;; esac;; -*) case $# in 1) echo 'Missing argument to '"$1" 1>&2 exit 1 ;; *) case "$confargs" in "") confargs="$1 $2";; *) confargs="$confargs $1 $2";; esac shift; shift ;; esac;; *) case "$curr" in "") curr="$1";; *) case "$rate" in "") rate="$1";; *) echo 'Extra argument '"$1"' ignored' 1>&2 ;; esac;; esac shift;; esac done # Check argument sanity and set flag-modifying "programs" # case "$rate" in "") echo "Usage: $0 [-n] [-x] [conf args] currency rate" 1>&2 exit 1 ;; esac case "$do" in "") echo "$0: at least one of -n and -x must be given" 1>&2 exit 1 ;; # The flag progs below accomplish this: # fd 3 = we won't touch # fd 4 = "p" (protected, we won't touch, extra copy to warn user) # fd 5 = "s" but not "x" (used only if -n was given) # fd 6 = "s" and "x" (used only if -x was given) # fd 7 = not "s" nor "x" (used only if -n was given) # fd 8 = not "s" but "x" (used only if -x was given) # Note that selection based on currency and category is done upstream # of this. We actually process all four categories of entry in all # runs; the operation of -n and -x is all done here - categories we # shouldn't be meddling with based on the flags given will simply be # empty 'cause $fprog won't write anything to them. *n*x*|*x*n*) # "p" (protected) -> fd 3 & 4 (don't touch) # "X" (already exchanged) -> fd 3 (don't touch) # "sx" (to be shared and exchanged) -> fd 6 (process) # "s" (to be shared) -> fd 5 (process) # "x" (to be exchanged) -> fd 8 (process) # "" (no action specified) -> fd 7 (process) fprog='Np?4W3WX.NX?3WX.Ns?Nx?6#5.#Nx?8#7..W' ;; *n*) # "p" (protected) -> fd 3 & 4 (don't touch) # "X" or "x" (exchange fodder or result) -> fd 3 (don't touch) # "s" (to be shared) -> fd 5 (process) # "" (no action specified) -> fd 7 (process) fprog='Np?4W3WX.NXNx+?3WX.Ns?5#7.W' ;; *x*) # "p" (protected) -> fd 3 & 4 (don't touch) # "X" or not "x" (not to be exchanged) -> fd 3 (don't touch) # "sx" (to be shared and exchanged) -> fd 6 (process) # "x" (to be exchanged) -> fd 8 (process) fprog='Np?4W3WX.NXNx!+?3WX.Ns?6#8.W' ;; esac # Get default currency and db file path from the configuration # defcurr=`conf defcurrency $confargs` || exit 1 if [ x"$defcurr" = x"$curr" ]; then echo "$0: currency must not be the default currency" 1>&2 exit 1 fi db=`conf -p db $confargs` || exit 1 # Make sure the db actually exists. (Normally this is not going to fail, but # it's too easy to use "-set db=testdb" when testing and get testdb wrong.) # if [ ! -r $db -o ! -w $db ]; then echo "$0: db file ($db) must be readable and writeable" 1>&2 exit 1 fi # Get the date, lock the db, set up temporary filename prefix, and # set up to delete temporaries on exit or signal. # now=`ndate` sslock "$db" $$ || exit 1 t=/tmp/exchange.$$ trap 'touch '$t'.touch; rm -f '$t'.*; exit' 0 1 2 15 # The select run picks out non-transactions in the relevant currency; # everything else goes to $t.result. The non-transactions are split up # based on their flags, per $fprog (set above), with the various file # descriptors redirected to temporary files. # # Alternatively, if -w was used, the select picks out transactions in # the relevant currency which involve the given "world account"; further # splitting exactly as for the non-transaction case above. # if [ ! "$worldacc" ]; then < "$db" select -split 3 -currency "$curr" -and -not -cat transaction 3> $t.result | flag "$fprog" 3> $t.noproc 4> $t.warn-p 5> $t.+s-x 6> $t.+s+x 7> $t.-s-x 8> $t.-s+x cat $t.noproc >> $t.result else < "$db" select -split 3 -currency "$curr" -and -cat transaction -and -account "$worldacc" 3> $t.result | flag "$fprog" 3> $t.noproc 4> $t.warn-p 5> $t.+s-x 6> $t.+s+x 7> $t.-s-x 8> $t.-s+x cat $t.noproc >> $t.result fi # Warn about anything marked p, or anything marked s but not x. p-flagged # entries have already been saved in the result file by $fprog, but # s-but-not-x entries haven't. # if [ -s $t.warn-p ]; then ( echo "$0: warning: the following not processed due to p flag:" < $t.warn-p sed -e 's/^/ /' ) 1>&2 fi if [ -s $t.+s-x ]; then ( echo "$0: warning: the following must be shared before exchange:" < $t.+s-x sed -e 's/^/ /' ) 1>&2 cat $t.+s-x >> $t.result fi # Okay. This leaves three categories to process: s-and-x, neither-s-nor-x, # and x-but-not-s. (Depending on which of -n and -x were given, some of these # may be empty even though such records appear in the input db, as mentioned # above.) # # $f is the temporary file being processed this time around the loop # $s and $x are flag programs to add the appropriate number of S and X flags # to entries that are being processed, after removing all s and x flags. # $ns is the flag string containing the s flag, if any, to be dropped into # the second automatically-generated entry. (This entry should get a copy # of the original entry's s flag. We don't care about the case where the # original entry has multiple s flags; this is semantically equivalent to # having just one s flag.) # for fx in +s+x -s-x -s+x; do f=$t.$fx # Don't do anything unless the file is nonempty. if [ -s $f ]; then # Set $s, $ns, $x (see above comment). case $fx in *+s*) s=f+Sf+S; ns=s;; *) s=""; ns="";; esac case $fx in *+x*) x=f+X;; *) x=f+Xf+X;; esac # Each entry becomes three entries: the original with the # flags field modified, an entry to negate the original but # to or from "exchange" instead of the account of the original, # and a third which is like the original but in the default # currency. (Remember, we're not doing anything with any # transaction entries in this script.) # # Rather than process each entry one by one, we first generate # all the modified-flags entries with "flag", then use a little # awk program to generate the other two entries for each # original. # # Remove all x and s flags, then add per $x and $s. The # resulting modified records then get appended to the result. # < $f flag '(Nx@f-x)(Ns@f-s)'"$x$s" >> $t.result # # Fields in this awk program: # $2 - entry date # $3 - flags # $5 - from account # $6 - to account # $7 - amount # $8 - currency # For each input entry, we generate two entries as described # above. "half" is to get rounding; it would be better to keep # proper track of fractional amounts, but that's more hair than # I want to get into now. # < $f awk -F: 'BEGIN { OFS = ":"; } { if ($7 < 0) half = -.5; else half = .5; $2 = "'$now'"; $7 = - $7; $3 = "AX"; if ($5 != "'$worldacc'") $5 = "exchange"; if ($6 != "'$worldacc'") $6 = "exchange"; print; $7 = int(('"$rate"'*-$7)+half); $3 = "AX'$ns'"; $8 = "'"$defcurr"'"; print; }' >> $t.result fi done # All done, copy the temporary back on top of the db, unlock the db, and exit. # Note that the trap command was set to run on trap 0, so it will run to remove # the temporary files on script exit. # cp $t.result "$db" ssunlock "$db"