[comp.sources.unix] v13i044: Rolodex-like filing system

rsalz@bbn.com (Rich Salz) (02/17/88)

Submitted-by: Larry Lippman <kitty!larry@UUNET.UU.NET>
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 <stdio.h>
X#include <curses.h>
X#include <signal.h>
X#include <string.h>
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

-- 
For comp.sources.unix stuff, mail to sources@uunet.uu.net.