Subject: v13i044: Rolodex-like filing system Newsgroups: comp.sources.unix Sender: sources Approved: rsalz@uunet.UU.NET Submitted-by: Larry Lippman Posting-number: Volume 13, Issue 44 Archive-name: rf The enclosed program is called rf(1L). It is a simple database program written in C which stores names, addresses, telephone numbers, and other data. It functions not unlike a "rotary file" for fast access, which is why it is called ``rf''. This program has been tested on several different System V ports with no problems. Some BSD ports may require some minor curses changes, but since I am not a BSD-person, I can't be specific. There should be no other processor/port dependencies beyond curses implementation. <> Larry Lippman @ Recognition Research Corp., Clarence, New York <> UUCP: {allegra|ames|boulder|decvax|rutgers|watmath}!sunybcs!kitty!larry <> VOICE: 716/688-1231 {hplabs|ihnp4|mtune|utzoo|uunet}!/ <> FAX: 716/741-9635 {G1,G2,G3 modes} "Have you hugged your cat today?" # This is a shell archive. Remove anything before this line, then # unpack it by saving it in a file and typing "sh file". (Files # unpacked will be owned by you and have default permissions.) # # This archive contains: # Makefile README rf.1 rf.c echo x - Makefile sed -e 's/^X//' > "Makefile" << '//E*O*F Makefile//' X# Makefile for rf(1L) X# Last revision 3 Jun 1985 X# X# define SYSDATA if system file other than /usr/local/lib/rf_data X# XSRC = rf.c XLIBS = -lcurses XCFLAGS = -O -s XDESTDIR = /usr/bin/ X Xrf: $(SRC) X cc $(CFLAGS) $(SRC) -o rf $(LIBS) X Xinstall: X chmod 555 rf X chgrp bin rf X chown bin rf X mv rf $(DESTDIR) X Xlint: X lint rf.c //E*O*F Makefile// echo x - README sed -e 's/^X//' > "README" << '//E*O*F README//' X The enclosed program is called rf(1L). It is a simple database Xprogram written in C which stores names, addresses, telephone numbers, Xand other data. It functions not unlike a "rotary file" for fast access, Xwhich is why it is called ``rf''. X In its present form it will search for a record by individual Xname, or by organization name. Any size search string may be used as Xthe key; command line parsing is intelligent enough such that quotes are Xunnecessary if the search string contains embedded whitespace. The output Xis displayed in a neat page-oriented fashion using curses(3X), with a paging Xcontrol where multiple matches are encountered. X The database file is easily configured using any editor. The Xdatabase file format is also designed so that it may be easily accessed by Xawk or sed for mailing list or other special applications. By setting an Xoption flag, the program will access a system-wide database, or a private Xdatabase file in the user's home directory. X While the program could have been written using awk and tput, it Xis substantially faster using C. In keeping with the recent discussion Xabout writing "new" UNIX functions, the increased speed is THE reason Xfor writing it in C. Typical search and display (9,600 baud) time on Xa 3B2 for one record from a 30 kilobyte database file is < 2.0 seconds; Xthis includes curses(3X) overhead - so it's pretty fast (the entire database Xfile is always searched to detect multiple matches). X The program is reasonably well commented, is intentionally written Xto be easily modified, and is reasonably well protected against users doing X"dumb" things. The program runs on three different Sys V versions, and Xshould probably run under BSD since it contains its own string search Xfunction and does not use getopt; the curses(3X) use is not particularly Xexotic. X The actual fields and field lengths were chosen to provide a Xprogram that fit the needs of my organization - an industrial R&D laboratory Xwhich communicates a GREAT deal with various other organizations. X The program is intentionally written so that the field definitions Xand their lengths can be easily modified for a particular organization. XFor example, our organization often sends telex and facsimile machine Xmessages, but many organizations NEVER send such messages - so these Xfields could be removed, allowing character expansion of the telephone Xnumber and uucp address fields. X Some people may not like my choice of video attributes for the Xpage display; obviously, this is easy to change. X The program fully passes lint, except for some silliness about Xunused curses(3X) functions. X If you are willing to accept my file name definitions and Xlocations, all you have to do is type ``make''... X X<> Larry Lippman @ Recognition Research Corp., Clarence, New York X<> UUCP: {allegra|ames|boulder|decvax|rutgers|watmath}!sunybcs!kitty!larry X<> VOICE: 716/688-1231 {hplabs|ihnp4|mtune|utzoo|uunet}!/ X<> FAX: 716/741-9635 {G1,G2,G3 modes} "Have you hugged your cat today?" //E*O*F README// echo x - rf.1 sed -e 's/^X//' > "rf.1" << '//E*O*F rf.1//' X.TH RF 1 Local X.UC 4 X.SH NAME Xrf \- "rotary file" database for names, addresses, etc. X.SH SYNOPSIS X.B rf X[ X.B \-f X] [ X.B \-l X] [ X.B \-o X] [ X.B search string X] X.SH DESCRIPTION X.B Rf Xis a simple "rotary file" database which stores names, addresses, Xtelephone numbers, comments, etc. for rapid retrieval in a formatted Xpage fashion. X.B Rf Xsearches its database file using a search string keyed to a X.I person's X.B name Xor X.I person's X.B organization. XMultiple matches are paginated using a single key command. X.PP XThe X.B \-f Xflag selects a private database file installed as X.B .rf_data Xin the user's home directory. Invoking X.B rf Xwithout this flag selects the database file X.B /usr/local/lib/rf_data Xavailable to all users. X.PP XThe X.B \-l Xflag lists only the X.B name Xor X.B organization Xfield matches without displaying the rest of the record, Xand is used for rapid Xscanning of the database file where multiple matches may occur. X.PP XThe X.B \-o Xflag searches for a match in the X.B organization Xfield, rather than the X.B name Xfield. X.PP X[ X.B search string X] may be any string of 1 to 72 characters in length; the string may Xcontain one or more instances of whitespace without having the Xstring enclosed in quotes. There is no upper <--> lower case Xconversion; the case presented is matched as is, and may be mixed. XPunctuation and whitespace Xembedded in the string is also matched; Xeach occurrence of whitespace must be limited to a length of Xone space. X.SH DATABASE FILE FORMAT XEach record consists of a minimum of two fields, Xwith all fields containing a two\-character identifier Xin the form of a letter followed by a colon. XThe data portion of the field may contain whitespace or any Xpunctuation to a maximum character length as described below. XEach record must begin with a X.B name Xfield; if there is an X.B organization Xfield, it must immediately follow the X.B name Xfield. XIf the record pertains to an X.B organization Xonly having no X.I person's X.B name Xentry, the X.B name Xfield identifier is still necessary, Xwith the rest of the field blank. XAll other fields are optional and may be included in any order. XRecords are separated by one blank line. X.PP XEach field is limited to one entry per record, Xexcept that X.B telephone, X.B telex, X.B fax Xand X.B uucp Xmay each have a maximum of two entries, Xfor line-order display as presented within the record; Xa maximum of four X.B comment Xentries is also permitted within the same record. X.PP X.nf X\fBN:name\fR 30 chars max. X\fBO:organization\fR 72 chars max. X\fBT:title\fR 24 chars max. X\fBD:department\fR 24 chars max. X\fBA:address\fR 72 chars max. X\fBP:telephone\fR 15 chars max. X\fBF:fax\fR 15 chars max. X\fBX:telex\fR 24 chars max. X\fBU:uucp\fR 24 chars max. X\fBH:home_telephone\fR 15 chars max. X\fBR:home_address\fR 72 chars max. X.fi X.PP XFields which exceed the above maximum number of characters Xresult in no error, but are silently truncated at Xthe maximum permissible length when displayed. XFields which contain incorrect header characters are ignored. XFor the sake of uniformity, comments in the database file Xthat are not intended for display Xshould be prefaced by the field header \fB#:\fR. X.SH EXAMPLE DATABASE FILE RECORD X.nf X.sh XN:Public, John Q. XO:Any Industry, Inc. XT:Systems Programmer XD:Widget R&D XA:123 Any Road, Anytown, NY 12345 XP:716/123-4567 XP:Ext 234 XF:716/123-4599 XX:12-3456 ANYINDNY XU:jqp@any.UUCP XH:716/123-9876 XR:456 Anybrook Lane, Anysuburb, NY 12354 XC:Writes CAD software for widgets XC:Has extensive experience with XYZNIX X.SH BUGS Xnone X.SH FILES X.nf X\fB$HOME/.rf_data\fR user private database file X\fB/usr/local/lib/rf_data\fR system-wide database file X.fi X.SH AUTHOR XLawrence G. Lippman, larry@kitty.UUCP //E*O*F rf.1// echo x - rf.c sed -e 's/^X//' > "rf.c" << '//E*O*F rf.c//' X/* X * rf(1L) A "rotary file"-like database for names, addresses, telephone X * numbers and related information. X * X * Copyright (c) 1985 X * by Lawrence Lippman, larry@kitty.UUCP X * Recognition Research Corp., Clarence, NY X * X * This program may be freely used and distributed, provided that X * it is not used for monetary or other commercial gain, and X * provided that this notice remains intact. X * X * Last revision: 3 Jun 1985 X */ X X#include X#include X#include X#include X X#ifndef SYSDATA X#define SYSDATA "/usr/local/lib/rf_data" /* system database file */ X#endif X X/* X * Global variables X */ X char name[31], title[25], org[73], dept[25], adr[73]; X char phone[2][16], telex[2][25], fax[2][16], uucp[2][25]; X char homephone[16], homeadr[65], comments[4][73]; X char Key[73], buf[80]; X int Org, File, List; X int hits, phlines, comlines; X int terminate(), more(), strsearch(); X FILE *f1, *fopen(); X Xmain(argc,argv) X char **argv; X{ X char filename[65], *getenv(); X int keywords; X X/* X * Set up defaults X */ X File = 0; /* Use system-wide database file, not user file */ X List = 0; /* Display full entry, not just hit list */ X Org = 0; /* Search by name, not organization */ X X/* X * Get options and search string X */ X (void) strcpy(Key,""); X keywords = 0; X X while(argc-- > 1) { X if(*argv[1] == '-') X switch(argv[1][1]) { X case 'l': X List = 1; X break; X case 'f': X File = 1; X break; X case 'o': X Org = 1; X break; X default: X usage(); X } X X else{ X if(keywords > 0) X (void) strcat(Key," "); X (void) strcat(Key,argv[1]); X keywords++; X } X argv++; X } X X if(strlen(Key) == 0) X usage(); X X/* X * Select and open database file X */ X if(File) X (void) sprintf(filename, "%s/.rf_data", getenv("HOME")); X else X (void) strcpy(filename, SYSDATA); X X if((f1 = fopen(filename,"r")) == NULL){ X (void) fprintf(stderr,"Cannot open data file %s\n",filename); X exit(-1); X } X X/* X * Catch signals and setup curses X */ X (void) signal(SIGINT, terminate); X (void) signal(SIGQUIT, terminate); X X initscr(); X if(List){ X idlok(stdscr,1); X setscrreg(0,19); X scrollok(stdscr,1); X } X X/* X * Read file, search for records, and do it to it... X */ X hits = 0; X X while(fgets(buf,80,f1) != NULL){ X if(buf[0] == 'N' && buf[1] == ':'){ X if(strlen(buf) == 2) X (void) strcpy(name,""); X else{ X (void) strxcpy(name,buf,2,30); X if(!Org){ X if(strsearch(name,Key)){ X if(hits > 0 && !List) X (void) more(); X (void) strcpy(org,""); X (void) clrrecord(); X (void) rdrecord(); X (void) display(); X hits++; X } X } X } X } X if(buf[0] == 'O' && buf[1] == ':'){ X (void) strxcpy(org,buf,2,72); X if(Org){ X if(strsearch(org,Key)){ X if(hits > 0 && !List) X (void) more(); X (void) clrrecord(); X (void) rdrecord(); X (void) display(); X hits++; X } X } X } X } X X (void) terminate(); X return(0); X} X X/* X * clrrecord: clear all display strings X */ Xclrrecord() X{ X int i; X X (void) strcpy(title,""); X (void) strcpy(dept,""); X (void) strcpy(adr,""); X (void) strcpy(homephone,""); X (void) strcpy(homeadr,""); X (void) strcpy(comments[0],""); X for(i = 0; i <= 1; i++){ X (void) strcpy(phone[i],""); X (void) strcpy(telex[i],""); X (void) strcpy(fax[i],""); X (void) strcpy(uucp[i],""); X } X} X X/* X * rdrecord: read the rest of a record following a "name" read X */ Xrdrecord() X{ X int phln, fxln, txln, uuln; X X phln = fxln = txln = uuln = 0; X phlines = 0; X comlines = 0; X X while(fgets(buf,80,f1) != NULL){ X X if(buf[1] != ':' || strlen(buf) <= 1) X return; X X switch(buf[0]) { X case 'O': X (void) strxcpy(org,buf,2,72); X break; X case 'T': X (void) strxcpy(title,buf,2,24); X break; X case 'D': X (void) strxcpy(dept,buf,2,24); X break; X case 'A': X (void) strxcpy(adr,buf,2,72); X break; X case 'P': X if(phln > 1) X break; X (void) strxcpy(phone[phln],buf,2,15); X phln++; X break; X case 'X': X if(txln > 1) X break; X (void) strxcpy(telex[txln],buf,2,24); X txln++; X break; X case 'F': X if(fxln > 1) X break; X (void) strxcpy(fax[fxln],buf,2,15); X fxln++; X break; X case 'U': X if(uuln > 1) X break; X (void) strxcpy(uucp[uuln],buf,2,24); X uuln++; X break; X case 'H': X (void) strxcpy(homephone,buf,2,15); X break; X case 'R': X (void) strxcpy(homeadr,buf,2,64); X break; X case 'C': X if(comlines > 3) X break; X (void) strxcpy(comments[comlines],buf,2,72); X comlines++; X break; X } X if(phln > 1 || txln > 1 || fxln > 1 || uuln > 1) X phlines = 1; X } X} X X/* X * display: Display the results of a search X */ Xdisplay() X{ X int l; X X if(List){ X if(hits == 0){ X erase(); X refresh(); X if(Org) X mvaddstr(0, 0, org); X else X mvaddstr(0, 0, name); X }else{ X if(Org) X printw("%s", org); X else X printw("%s", name); X X } X refresh(); X return(0); X } X X erase(); X refresh(); X X attron(A_REVERSE); X mvaddstr(0, 0, "NAME"); X mvaddstr(0, 32, "TITLE"); X mvaddstr(0, 56, "DEPT"); X mvaddstr(3, 0, "ORGANIZATION NAME & ADDRESS"); X mvaddstr(7, 0, "TELEPHONE"); X mvaddstr(7, 16, "TELECOPIER"); X mvaddstr(7, 32, "TELEX"); X mvaddstr(7, 56, "UUCP"); X mvaddstr(10 + phlines, 0, "HOME TELEPHONE"); X mvaddstr(10 + phlines, 16, "HOME ADDRESS"); X mvaddstr(13 + phlines, 0, "COMMENTS"); X attroff(A_REVERSE); X X mvaddstr(1, 0, name); X mvaddstr(1, 32, title); X mvaddstr(1, 56, dept); X mvaddstr(4, 0, org); X mvaddstr(5, 0, adr); X for(l = 0; l <= phlines; l++){ X mvaddstr(8 + l, 0, phone[l]); X mvaddstr(8 + l, 16, fax[l]); X mvaddstr(8 + l, 32, telex[l]); X mvaddstr(8 + l, 56, uucp[l]); X } X mvaddstr(11 + phlines, 0, homephone); X mvaddstr(11 + phlines, 16, homeadr); X for(l = 0; l <= comlines; l++) X mvaddstr(14 + phlines + l, 0, comments[l]); X X refresh(); X return(0); X} X X/* X * more: Prompt for display of entries when multiple hits occur X */ Xmore() X{ Xagain: X attron(A_REVERSE); X mvaddstr(20, 0, "MORE HITS: CONTINUE?"); X attroff(A_REVERSE); X mvaddstr(20, 22, "[y] [n] "); X refresh(); X switch(getch()){ X case 'n': X case 'N': X (void) terminate(); X case 'y': X case 'Y': X return(0); X } X mvaddstr(20, 31, " "); X goto again; X} X X/* X * terminate: Exit gracefully X */ Xterminate() X{ X (void) fclose(f1); X X move(LINES-1, 0); X refresh(); X endwin(); X exit(0); X} X X/* X * strsearch: Search for any occurence of string "t" in string "s"; X * return 1 if a match found, otherwise return 0 X */ Xstrsearch(s,t) X char s[80], t[80]; X{ X register i, n, nn; X int slength, tlength; X char st[80]; X X slength = strlen(s); X tlength = strlen(t); X if(slength == 0 || tlength == 0) X return(0); X X for (i = 0; i < slength; i++){ X n=0; X for(nn = i; nn < i + tlength; nn++){ X if(nn > slength) X return(0); X st[n] = s[nn]; X n++; X } X st[n] = '\0'; X if (strcmp(st,t) == 0) X return(1); X } X return(0); X} X X/* X * strxcpy: Copy string "t" to string "s", with "offset" characters in X * string "t" skipped before the copy, and with a maximum of X * "maxs" characters [not including NULL] copied to string "s" X */ Xstrxcpy(s,t,offset,maxs) X char s[80], t[80]; X int offset, maxs; X{ X register n, nn; X int tlength; X X nn = 0; X tlength = strlen(t); X X for(n = offset; n <= tlength + 1; n++){ X s[nn] = t[n]; X if(t[n] == '\0') X return(0); X nn++; X if(nn == maxs){ X s[nn + 1] = '\0'; X return(0); X } X } X return(0); X} X X/* X * usage: Display usage error message and exit X */ Xusage() X{ X (void) fprintf(stderr,"usage: rf [-f] [-l] [-o] searchstring\n"); X exit(-1); X} //E*O*F rf.c// exit 0