#!/bin/zsh # (c) Dominik Vogt, 12-Jul-1998, domimink.vogt@gmx.de # # tele - a (phone) database implemented as a zsh script for zsh 3.0.5. The # data is part of the script. # # # NOTE: For performance reasons you will find the documentation at the end of # this script. # # # COPYRIGHT NOTICE: # # 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### # customizing section # ############################################################################### # reserved words: all, com, nl, lots of variable names in UPPERCASE ##### default options ##### # Change these if you like. #DEFAULT_OPTIONS=(-s -f +o) # zsh 3.0.5 getopts bug patch DEFAULT_OPTIONS=(-s -f -O) ##### list of all defined keys ##### # 'nl' must be the first entry and can be used to insert a newline character. # The value of this key is printed on the new line. KEYS=(nl tit nam fn sn nn occ cmp add po zip cit pho fax ema web hou bir ban bcn acc cus mis) ##### long form of the keys ##### # The first entry is a mask for the field length. All entries should be exactly # the same length, end with a space and must be in the same order as in the # KEYS array. KEYNAMES=(\ " " \ "title: " \ "name: " \ "first name: " \ "surname: " \ "nickname: " \ "occupation: " \ "company: " \ "address: " \ "PO box: " \ "zip: " \ "city: " \ "phone: " \ "fax: " \ "email: " \ "website: " \ "hours/busi.: " \ "birthday: " \ "bank: " \ "bank code #: " \ "account: " \ "customer #: " \ "misc.: " ) ##### group definitions ##### # Logical groups of keys. Groups are arrays of keynames as defined above in the # array KEYS. The 'all' group must be defined and must contain all of the keys # except 'nl' and 'com' which are reserved words. The order of the keys does # not matter, however. A '\\' prefix to the key names has a special meaning if # short output format is used (-s option): # # (substitute 'current key' for c, 'previous key' for p, 'current group' for g) # # IF c is in g # AND p ('com' and 'nl' do not count) has been printed also # AND key name of c begins with '\\' # AND p is the same as c or occurs before c in g # AND all keys between p and c begin with '\\' (in g) # THEN print current key on the same line as the last one printed. # # Note that the '\\' only occurs in the group arrays, not in the database. # The characters ',;.:' work exactly the same with the exception that they # will be printed before the space if all above conditions are met. # all=(nam tit \\fn \\sn \\nn occ cmp add po zip \\cit pho fax hou ema web bir ban bcn acc cus mis) add=(add po zip \\cit) pho=(pho fax hou) nam=(nam tit \\fn \\sn \\nn) job=(occ cmp) per=(bir) web=(ema web) acc=(ban bcn acc cus mis) # Special groups for printable output. padd=(,add \\po ,zip \\cit) ppho=(,pho ,fax ,hou) psn=(nam \\sn) pfn=(,fn ,tit) ##### list of all defined groups ##### GROUPS=(all nam pho add job per web acc prt) ##### separator symbol ##### # The character in the database used to separate the key names and values. # You must change it in the function 'break_stanza_lines' manually: # Replace 's:x:' with 's:y:' where x is the old separator and y the new one. KEY_SEPARATOR="|" ##### default query ##### # The groups you want to see by default (e.g. name and telephone number). DEFAULT_QUERY=(nam add pho) ##### default print query ##### # Special default query for the -p option. This is useful if you want to print # your the whole database (or selected keys) in a compressed format. PRINT_QUERY=(psn pfn padd ppho) ##### comment setup ##### # The default strings printed before and after a comment. COM_PREFIX="" COM_SUFFIX="" ############################################################################### # do not edit below here # ############################################################################### #------------------------- # zsh configuration #------------------------- # reset options setopt LOCALOPTIONS emulate -R zsh setopt EXTENDEDGLOB setopt SHWORDSPLIT unsetopt KSHARRAYS unsetopt FUNCTIONARGZERO unsetopt ALLEXPORT # just to make sure alias echo='builtin echo' alias read='builtin read' #------------------------- # global variables #------------------------- local G_CUST="" # options local O_ARRAY="" local O_FAST="" local O_FORMAT="" local O_GSEPARATE="" local O_QTYPE="" local O_QUERY="" local G_SEP="" #------------------------- # functions #------------------------- #------------------------- # error handling functions #------------------------- function usage () { echo "usage: ${0##*/} [ options ] [ regexp ]" if [[ -z "$1" ]] ; then echo " ${0##*/} -h for help" exit 2 else echo "options:" echo " -a Select all keys in the order they appear in KEYS." echo " -c,+f Complex search. Search keys as well as record labels." echo " -C,-D string" echo " Specify the string printed before (-C) and after (-D) a comment." echo " -f,+c Fast search. Only the record labels are searched." echo " -g list|group[,group]..." echo " 'tele -g list' for a list of groups. No spaces must occur" echo " between the commas and the groups." echo " -h Print this help text." echo " -k list|key[,key]..." echo " 'tele -k list' for a list of keys. Keys with a \\-prefix (e.g." echo " \\tel are printed on the same line as the previous key if short" echo " output format (-s option) is used. No spaces are allowed in the" echo " list." echo " -l Long output style. Each key is printed on a separate line." echo " -o,+o,-O Print keys in the order of the selected groups. Otherwise (or" echo " if the +o or -O option is used) the keys are printed in the" echo " order they occur in the database." echo " -p Format output for printing. No newline is inserted between the" echo " groups. Sets the -o option. This may be changed by specifying +o" echo " anywhere after the -p option." echo " -s,+l Short output style." echo "default options: $DEFAULT_OPTIONS -g ${(j:,:)DEFAULT_QUERY}" echo "additional -p default options: -o -g ${(j:,:)PRINT_QUERY}" exit fi } check_group_all () { if [[ ! $[ ${#all} + 1 ] = ${#KEYS} ]] ; then echo "ERROR: Group 'all' must contain all elements of 'KEYS' except 'nl'." echo "Please check your configuration at the beginning of this script." echo fi } function list_valid_keys () { local -i I=2 local -i J local -i PAD check_group_all if [[ ! ${#KEYS} = ${#KEYNAMES} ]] ; then echo "ERROR: 'KEYS' and 'KEYNAMES' have a different number of entries!" echo "Please check your configuration at the beginning of this script." echo "Possibly offending pairs of key/name:" echo J=${#KEYS} (( $J > ${#KEYNAMES} )) && J=${#KEYNAMES} I=2 repeat $J ; do if [[ ! ${(L)KEYS[1]} = ${(L)KEYNAMES[1]} ]] ; then echo "$KEYS[$I] - ${KEYNAMES[$I]%: *}" fi I=$I+1 done exit 3 fi echo "valid keys:" echo PAD=${#KEYNAMES[1]}-1 repeat $[ (${#KEYS}+1)/3 ] ; do echo " ${(r:5:: :)KEYS[$I]}${(r:$PAD:: :)${(S)KEYNAMES[$I]#:}}\c" echo " ${(r:5:: :)KEYS[$I+1]}${(r:$PAD:: :)${(S)KEYNAMES[$I+1]#:}}\c" echo " ${(r:5:: :)KEYS[$I+2]}${(r:$PAD:: :)${(S)KEYNAMES[$I+2]#:}}" I=$I+3 done exit $1 } function list_valid_groups () { check_group_all echo "valid output groups:" echo " default $GROUPS[@]" echo "'default' equals to '$DEFAULT_QUERY'." exit $1 } #------------------------- # break_stanza_lines #------------------------- function break_stanza_lines () { [[ -n "$S_BROKEN" ]] && return S_BROKEN=1 L_DATA=(${(s:|:)STANZA}) L_NUM=$[${#L_DATA}/2] } #------------------------- # printing functions #------------------------- function print_line_long () { local S [[ -n "$L_PANY" ]] && echo || L_PANY=1 S=${KEYS[(i)$L_DATA[$LK]]} [[ $P_LAST = $S ]] && S=1 || P_LAST=$S echo "$KEYNAMES[$S]$L_DATA[$LV]\c" } function print_line_short () { [[ -n "$L_PANY" ]] && echo "$1\c" || L_PANY=1 echo "$2$L_DATA[$LV]$3\c" } function print_stanza () { local -i I local -i J=1 local -i LK local -i LV local -i NKEY local -i KEY local -i K_NUM local K_ARRAY local S local L_PRINTED local P_LAST="" break_stanza_lines # print header [[ $O_FORMAT = l ]] && echo "--> $L_DATA[1]" # print groups repeat $G_NUM ; do LK=2 LV=3 KEY=1 S="\$$G_ARRAY[$J]" K_ARRAY=( ${(e)S}[@] ) K_NUM=${#K_ARRAY} repeat $L_NUM ; do case $L_DATA[$LK] in com|nl) if [[ -n "$L_PRINTED" ]] ; then if [[ $L_PRINTED = com ]] || [[ $L_DATA[$LK] = nl ]] ; then echo [[ $O_FORMAT = l ]] && echo "$KEYNAMES[1]\c" L_PANY="" print_line_short \\n "$COM_PREFIX" "$COM_SUFFIX" L_PRINTED=com else print_line_short " " "$COM_PREFIX" "$COM_SUFFIX" L_PRINTED=com fi fi;; **) NKEY=${${K_ARRAY}[(i)[\,;.:]#$L_DATA[$LK]]} if (( $NKEY > $K_NUM )) || (( $NKEY < 1 )); then L_PRINTED="" KEY=1 else if [[ $O_FORMAT = l ]] ; then print_line_long elif (( $KEY > $NKEY )) ; then if [[ $O_FORMAT = p ]] ; then case "${K_ARRAY[$NKEY][1]}" in ,|\;|.|:) print_line_short "${K_ARRAY[$NKEY][1]} ";; **) print_line_short "$G_SEP";; esac else print_line_short "$G_SEP" fi else I=$NKEY while true ; do case "${K_ARRAY[$I][1]}" in \\|,|\;|.|:) ;; **) if (( $I > $KEY )) && [[ ! $I = $NKEY ]] ; then print_line_short "$G_SEP" break fi;; esac I=$I-1 if (( $I <= $KEY )) ; then case "${K_ARRAY[$NKEY][1]}" in \\) print_line_short " ";; ,|\;|.|:) print_line_short "${K_ARRAY[$NKEY][1]} ";; **) print_line_short "$G_SEP";; esac break fi done fi KEY=$NKEY L_PRINTED=1 fi;; esac LK=$LK+2 LV=$LV+2 done J=$J+1 done } #------------------------- # function complex_query #------------------------- function complex_query () { ### [[ -n "${(MS)${(L)STANZA}#${(L)O_QUERY}}" ]] } #------------------------- # parse stanzas #------------------------- function parse_stanzas () { local STANZA="" local S_BROKEN="" local -i G_NUM local -i G_ARNUM local -i G_DFNUM local S_PANY="" local G_ARRAY local -i L_NUM local L_DATA local L_PANY="" local K_MERGED="" local -i I local S local T_ARRAY local T_NUM if [[ -n "$O_GSEPARATE" ]] ; then G_ARRAY=($O_ARRAY) G_ARNUM=${#G_ARRAY} else G_ARRAY=(K_MERGED) G_ARNUM=1 I=1 repeat ${#O_ARRAY} ; do S="\$$O_ARRAY[$I]" K_MERGED=( $K_MERGED[@] ${(e)S}[@] ) I=$I+1 done fi G_NUM=$G_ARNUM # parse input file read 'STANZA' while [[ -n "$STANZA" ]] ; do S_BROKEN="" if [[ $O_QTYPE = f ]] || complex_query; then if [[ -z "$S_PANY" ]] ; then S_PANY=1 else [[ $O_FORMAT = p ]] && echo || echo "\n" fi L_PANY="" print_stanza if [[ -z "$L_PANY" ]] ; then echo "no keys selected, printing whole record" T_ARRAY=($G_ARRAY) T_NUM=$G_NUM G_ARRAY=(all) G_NUM=1 print_stanza G_ARRAY=($T_ARRAY) G_NUM=$T_NUM fi fi read 'STANZA' done [[ -n $S_PANY ]] && echo } #------------------------- # function split_stanzas #------------------------- function split_stanzas () { sed -n " bskipline :seekhead n :skipline /^EOF$/q /^[^[:space:]].*:$/bhead bseekhead :head h /$O_FAST/bseektail bseekhead :seektail n /^[[:space:]]*$/btail s/^\([^$KEY_SEPARATOR]*\)$/\1|/g H bseektail :tail x s/\n[[:space:]]*/$KEY_SEPARATOR/g s/$KEY_SEPARATOR$KEY_SEPARATOR/$KEY_SEPARATOR $KEY_SEPARATOR/g p x bseekhead " } #------------------------- # parse options #------------------------- local -i I local S while getopts ":acC:D:fg:hk:loOps" OPT $DEFAULT_OPTIONS $*; do case $OPT in a) O_ARRAY=(all);; c|+f) O_QTYPE=c;; C) COM_PREFIX="$OPTARG";; D) COM_SUFFIX="$OPTARG";; f|+c) O_QTYPE=f;; g) [[ -z "$OPTARG" ]] && list_valid_groups 0 if [[ $OPTARG = default ]] ; then O_ARRAY=( $DEFAULT_QUERY[@] ) break fi I=${#O_ARRAY}+1 O_ARRAY=( $O_ARRAY[@] ${(s:,:)OPTARG} ) repeat $[ ${#O_ARRAY} - $I + 1 ] ; do S=${${GROUPS}[(i)${O_ARRAY[$I]}]} if (( $S < 1 )) || (( $S > ${#GROUPS} )) ; then [[ $O_ARRAY[$I] = list ]] || echo "invalid group $O_ARRAY[$I]" >&2 list_valid_groups 2 fi I=$I+1 done;; h) usage help;; k) [[ -z "$OPTARG" ]] && list_valid_keys 0 I=${#G_CUST}+1 G_CUST=( $G_CUST[@] ${(s:,:)OPTARG} ) repeat $[ ${#G_CUST} - $I + 1 ] ; do S=${${KEYS}[(i)${G_CUST[$I]#+}]} if (( $S < 2 )) || (( $S > ${#KEYS} )) ; then [[ $G_CUST[$I] = list ]] || echo "invalid key $G_CUST[$I]" >&2 list_valid_keys 2 fi I=$I+1 done;; l|+s) G_SEP=\\n O_FORMAT=l;; o) O_GSEPARATE=1;; O|+o) O_GSEPARATE="";; p) O_FORMAT=p O_GSEPARATE=1 G_SEP=" " O_ARRAY=( $PRINT_QUERY[@] );; s|+l) G_SEP=\\n O_FORMAT=s;; **) echo "illegal option '${OPTARG:-$OPT}'" >&2 usage;; esac done OPTIND=$[ $OPTIND - ${#DEFAULT_OPTIONS} ] if (( $# > $OPTIND )) ; then # trailing parameters? echo "trailing parameters after pattern '$argv[$OPTIND]'" >&2 usage elif [[ $O_QTYPE = f ]] ; then # fast search O_FAST="${(L)${argv[$OPTIND]}}" else # complex search O_QUERY="$argv[$OPTIND]" fi # output groups if [[ ! ${#G_CUST} = 0 ]] ; then O_ARRAY=(G_CUST) elif [[ ${#O_ARRAY} = 0 ]] ; then O_ARRAY=($DEFAULT_QUERY) fi #------------------------- # beginning of data #------------------------- # pipe the data group to the parse function (the braces are necessary here) #{ split_stanzas } <<- "EOF" { split_stanzas | parse_stanzas } <<- "EOF" fvwm (fvwm-workers): nam|fvwm web|http://fvwm.org com|fvwm home page web|ftp://fvwm.org/pub/fvwm/ com|ftp site web|http://fvwm.org/fvwm-cats/ com|fvwm cats page ema|fvwm@fvwm.org ema|fvwm-workers@fvwm.org ema|fvwm-announce@fvwm.org suse gmbh (suse, linux): nam|SuSE GmbH (SuSE Linux) pho|0911/7406331 fax|0911/7417755 com|Fax add|Schanzaeckerstr. 10 zip|90443 cit|Nuernberg acc|115569 web|www.suse.de universitaet dortmund, unido: nam|University Dortmund (UniDo) pho|0231/7551 com|Central pho|0231/7552256 com|Students office (Emil-Figge Str. 66) add|Emil-Figge Str. zip|44221 cit|Dortmund vogt, dominik (domivogt): fn|Dominik sn|Vogt nn|Avatar occ|fvwm hacker and zsh fanatic cmp|fvwm-workers mailing list :-) cit|Althengstett ema|dominik.vogt@gmx.de web|http://fvwm.org EOF # The last line before the EOF *must* be empty or the last stanza # will not be used!! # DOCUMENTATION # # # Note: # # You can find detailed examples at the end of this documentation. # # OVERVIEW: # # This script implements a database based solely on zsh functionality. The # only external program used is sed (for performace reasons only). The # purpose of this script is to provide a small, fast, extendable and portable # database. It began as a simple phone and address database, but I added new # types of datasets as I needed them. Today I use it for all the data and # numbers that were strewn over piles of paper, booklets and files. You can # run this script on any platform that provides a bug free sed command. # Should this not work you can still 'vi =tele' this file and look up the # needed record yourself. # # The data is stored in records that are described by stanzas of a certain # format. A stanza consits of a title and one or more key/value pairs. By # default a query is limited to the title, but more complex (but slower) # queries that look at the complete record can be used too. # # The keys are abbreviations for the interesting data in the record like 'fn' # (first name), 'sn' (surname), 'pho' (phone number) and so on. The keys are # assembled in groups for convenience reasons. The 'add' (address) group # consists of the keys 'add' (address), 'po' (PO box), 'zip' and 'cit' (city) # for example. The default query prints the contents of the 'nam' (name), # 'pho' (phone) and 'add' groups. # # In general you have to specify which records and which keys from these # records you want to see. # # # ADDING RECORDS: # # All records are entered in stanzas at the end of this script. The format of # a record (stanza) is this: # # TITLE: # KEY|CONTENTS # KEY|CONTENTS # ... # # # The TITLE must begin at the start of a line and end with a colon (':'). # The following lines must begin with a tab followed by the KEY, a '|' and # the data itself. Multiple keys of the same type can be used in one stanza. # The last line of the stanza *must* be empty to separate if from the # following stanzas. For example: # # vogt, dominik (domivogt): # fn|Dominik # sn|Vogt # nn|Avatar # cit|Althengstett # # vogt, melanie (melle): # ... # # Note: although you can put anything you like in the title, remember that # most queries look at the titles only. So be sure to put all the information # you prefer into the title. Personally I put the first name, surname, # nickname and other keywords like 'doctor' there. Using lower case letters # only in the title makes querying easier. # # Note: Do not use the separator character ('|'). If you need it you can # configure a different character (see customizing section). # # The preconfigured keys are: # # tit (title) # nam (name) # fn (first name) # sn (surname) # nn (nickname) # occ (occupation) # cmp (company) # add (address) # po (PO box) # zip (zip) # cit (city) # pho (phone) # fax (fax) # ema (email) # web (website) # hou (hours of business) # bir (birthday) # ban (bank) # bcn (bank code number) # acc (account) # cus (customer number) # mis (miscellaneous) # # and two special keys: # # nl (newline) # com (comment) # # All comments and newlines are printed whenever the previous line of the # stanza is printed. E.g. # # pho|01234/567890 # com|home # nl| # pho|09876/54-3210 # com|office # # would be printed as # # Phone: 01234/567890 home # # Phone: 09876/54-3210 office # # # SYNOPSIS: # # tele [ options ] [ search word ] # # The search word determines which records in the database are selected # (and hence printed out). If no search word is gicen, all records are # selected. When using the fast search option (-f or +c), tele looks for # matches in the label of each record (the first line). The search word # is a regular expression as implemented by your version of sed then. With # a complex search (-c or +f option) the contents of all keys are searched. # For performance reasons only simple strings are supported (to be honest, # now that I write this manual I don't understand my script any more. If # you want to implement regexp complex search you will have to rewrite the # 'complex_search' function in this script. If you do so, please send me # your version of the function.) In either case the match is case # insensitive. # # output options: # # -a # Select all keys in the order they appear in KEYS. # # -C string # -D string # Specify the string printed before (-C) and after (-D) a comment. # # -g list|group[,group]... # Use 'tele -g list' for a list of defined groups (see customizing # section). If a comma separated list of groups is given, all # information in the selected records in one of the given groups # is printed. No spaces must occur between the commas and the groups. # # -k list|key[,key]... # # Use 'tele -k list' for a list of defined keys (see customizing # section). If a comma separated list of keys is given, the given # keys in the selected records are printed. No spaces must occur # between the commas and the keys. # # -l # Long output style. Each key is printed on a separate line with a # label. # # +l # Same as -s. Turns off the -l option. # # -o # Print keys in the order of the selected groups. # # +o # Print keys in the order they occur in the database. # # -O Same as +o. # # -p # Format output for compact printing. No newline is inserted between the # groups. Sets the -o option. This may be changed by specifying +o # anywhere after the -p option. # # -s # Short output style. No labels are printed, information such as names # and titles are printed on a single line. # # +s # Same as -l. Turns off the +s option. # # record selection options: # # -c # Complex search. Search keys as well as record labels. # # +c # Same as -f. Turns off the -c option. # # -f # Fast search. Search only the record labels for matvches. # # +f # Same as -c. Turns off the -f option. # # other options: # # -h # Print a help text. # # default options: # # -s -f -O -g nam,add,pho # # default options when using the -p option: # # -s -f -o -g psn,pfn,padd,ppho # # # CUSTOMIZING: # # Please see the customizing section at the beginning of the script. I hope # the comments there are detailed enough to allow you to tinker with the # details of my script. # # # EXAMPLES: # # I have prepared a few records to demonstrate the usage of this script. # # # Simple query with default options (short output): # # # tele suse # S.u.S.E. GmbH (SuSE Linux) # 0911/7406331 # 0911/7417755 Fax # Schanzaeckerstr. 10 # 90443 Nuernberg # # # Long output format: # # # tele -l suse # --> suse gmbh (suse, linux): # name: SuSE GmbH (SuSE Linux) # phone: 0911/7406331 # fax: 0911/7417755 Fax # address: Schanzaeckerstr. 10 # zip: 90443 # city: Nuernberg # # # Selecting a single key: # # # tele -l -k occ domi # --> vogt, dominik (domivogt): # occupation: fvwm hacker and zsh fanatic # # # Fast versus complex search: # # # tele -lf avatar # --> fvwm (fvwm-workers): # name: fvwm # # # tele -lc fvwm # --> fvwm (fvwm-workers): # name: fvwm # # --> vogt, dominik (domivogt): # first name: Dominik # surname: Vogt # nickname: Avatar # city: Althengstett # # # Viewing the complete record # # # tele -a domi # Dominik Vogt Avatar # fvwm hacker and zsh fanatic # fvwm-workers mailing list :-) # Althengstett # dominik.vogt@gmx.de # http://fvwm.org # # # Customized comment delimiters: # # # tele -C "<<" -D ">>" -a fvwm # fvwm # http://fvwm.org <> # ftp://fvwm.org/pub/fvwm/ <> # http://fvwm.org/fvwm-cats/ <> # fvwm@fvwm.org # fvwm-workers@fvwm.org # fvwm-announce@fvwm.org # # # Ordering keys in output: # # # tele -o -g add,nam suse # Schanzaeckerstr. 10 # 90443 Nuernberg # SuSE GmbH (SuSE Linux) # # # tele -O -g add,nam suse # SuSE GmbH (SuSE Linux) # Schanzaeckerstr. 10 # 90443 Nuernberg # # # BUGS: # # Due to a bug in the getopts builtin in zsh 3.0.5 all command line options # with a leading '+' do not work. Use the corresponding options with # beginning with '-' instead. # # Some implementations of sed don't understand \n to denote a newline in # search patterns. If you have such a version of sed the script will not # print the records you want. In this case you have to replace this line of # the embedded sed script: # # s/\n[[:space:]]*/$KEY_SEPARATOR/g # # with these two: # # s/\ #[[:space:]]*/$KEY_SEPARATOR/g # # I.e. insert a \ followed by a line break instead of the \n.