From bobm@rtech.UUCP (Bob Mcqueer) Wed May 21 14:25:12 1986 Path: seismo!lll-crg!lll-lcc!unisoft!mtxinu!rtech!bobm From: bobm@rtech.UUCP (Bob Mcqueer) Newsgroups: net.sources Subject: Yet Another Newsreader - vn (PART 1 of 3) Message-ID: <267@rtech.UUCP> Date: 21 May 86 18:25:12 GMT Distribution: net Organization: Relational Technology Inc, Alameda CA Lines: 934 For the last couple of years, I've used my own newsreader, which has also enjoyed favor with some of my co-workers. It is a completely different interface than the other readers, its main goal being to allow rapid scan of a great volume of news. Many people have made suggestions which found their way into this program. Features: Once data has been accumulated, the interface is very rapid. You see whole screens of titles at a time, choosing the ones you want to read / save / print via cursor positioning, search strings, marks, or "choose everything". Update of .newsrc under user control, not tied to article presentation. Unpacks digest articles, lets you manipulate them as normal articles. Since it IS a different interface, I would advise you to read the manual page first, and save your old .newsrc the first time you run it, in case you misunderstand or decide you don't like the way it works regarding updating the .newsrc. In a way, this reader reflects my personal biases, not providing some features I could care less about, providing some things I or others thought missing. Favorable responses from a variety of people have convinced me that my biases are shared. Bob McQueer {amdahl|sun|mtxinu}!rtech!bobm cut here ----------------------------------------- #!/bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #!/bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # README # vn.man # mf.nore # mf.re # config.h # head.h # reader.h # tty.h # tune.h # vn.h # This archive created: Tue May 20 12:49:33 1986 export PATH; PATH=/bin:$PATH echo shar: extracting "'README'" '(5680 characters)' if test -f 'README' then echo shar: over-writing existing file "'README'" fi cat << \SHAR_EOF > 'README' Installation procedure steps for UCB 4.2-like systems: 1) Edit file config.h, which defines system dependent parameters such as spool directory, and so on. 2) decide which makefile to use, edit it to fill in the correct libraries for your system. See notes 2) below for explanation. 3) make vn 4) put the executable where you want it. put the man page, vn.man where you want it. roff it with -man to print it out. Notes: 1) If you modify the default mailer, poster, printer or editor, this is their invocation, ufile being a tempfile name. "mailer path /dev/null" "editor ufile" Should you change the hash table size, a few convenient prime numbers: 211 503 809 1009 1201 1511 2003 2503 3001 4001 5003 The hash table uses linear probe to resolve collisions. Works well as long as the density stays reasonably low. I would say to keep HASHMAX / HASHSIZE around 60%. The number of hashtable entries will actually be the number of active newsgroups. You may also be interested in the header file "tune.h" which contains some sizing / performance affecting parameters. 2) There are two makefile templates, both very simple, because I want to make you think about whether you have the system V regular expression library or not. If you have regex and regcmp (as opposed to re_exec and re_comp, the ucb regular expression library), I advise you to use them. regex and regcmp are used because they have the ability to keep several regular expressions compiled at once. The file "reg.c" is an implementation of regcmp and regex on top of the ucb calls, with a special procedure called "regfree" used in place of the generic "free" call used with regcmp / regex. Use the system V calls if you have them available because: i) They should be faster than the reg.c code, which recompiles the "current" ucb string every time you switch regular expressions. ii) I briefly checked out reg.c once, and it seemed to work. Our system has the "real" calls available, so I run with those. reg.c hasn't been used much. If you have regex/regcomp: mf.re is the makefile template. Fill in your local libraries (termcap), the library to find regex/regcmp in. This makefile defines "regfree" simply to be "free", and doesn't link the "reg" object. If you don't: mf.nore is the makefile template. Fill in your local libraries (termcap). This makefile pulls in the "reg" object, which implements regcmp and regex on top of the ucb library, with the special "regfree" call. ucb.c defines a couple more trivial system V calls. Might as well use it anyhow. We are currently running under Ultrix on a microvax, and in fact, the Ultrix tmpnam() doesn't work right. Ultrix has regcmp/regex in the normal c runtime library, mf.re reflects this. Something pretty close to the current vn version used to run here on a VAX 780 under BSD 4.2. SYSTEM V: I would be interested if somebody would #ifdef this thing up for system V. Very early versions ran on system V. I would hope that it would not be terribly difficult to convert. "sig_set.c" contains all the signal handling. "tty_set.c" contains all the ioctl() calls. These two things would obviously have to change drastically. You would have to #define index() to be strchr(). The current working directory interface is different, if I remember properly, envir_set.c bears study. You should be able to dispense with ucb.c. NEWS VERSIONS: I know there's a lot of news versions running out there. We seem to be running something called B2.10.2, for those of you whom that signifies anything to. Something to look at is your "active" file. I expect to find a newsgroup followed by the high, then low, spooling numbers (followed by a y/n for posting priveledge, which I don't care about). Earlier news versions used to only have a single spooling number. vn is written to handle this, assuming 0 for a low spool. My basic assumptions are that the "active" file gives the newsgroups and spooling information and that articles will be numeric filenames living in the directory obtained by replacing periods with slashes in the newsgroup, and prefixing the spool directory. I hope these really are "basic" assumptions that work across all versions. vn is intended to be highly fault-tolerant regarding what it finds for header lines - it may not be able to make sense out of an "article", but it's willing to let you look at it anyway. RESOURCE USE: vn should look like people sitting in an editor once it is done with its reading phase. During the reading phase, it is beating mercilessly on the spooling directory, reading file after file. I have thought from time to time about having a daemon do this work periodically, building a master file of title information for vn to access. Its reading phase would then be a "pause" rather than a "phase", with the penalty that you couldn't read anything until the daemon had gotten around to it. vn maintains a large temporary file containing the users page screens. Again, it should look a lot like the user is using an editor which has a temp file out there for its edit buffer. MAX_C in "tune.h" can be used to help control the size. malloc() usage - Around 40 bytes per newsgroup is dynamically allocated, plus storage for character strings and the current screen structure. My guess would be on the order of 20K. It's all "permanent" information maintained for the entire session, hence unfreed. SHAR_EOF echo shar: extracting "'vn.man'" '(12738 characters)' if test -f 'vn.man' then echo shar: over-writing existing file "'vn.man'" fi cat << \SHAR_EOF > 'vn.man' .TH VN 1 2/1/85 .UC .SH NAME vn - visual news reader .SH SYNOPSIS .I vn .SH DESCRIPTION .I Vn is a news reader which uses the same .B .newsrc file as .I readnews (1), but displays and interacts differently. It is aimed at allowing you to rapidly scan a large number of newsgroups, looking for something you want to read. The major premise is that you will be interested in a small number of articles, but will be interested in keeping tabs on a large number of newsgroups which may contain something interesting from time to time. It also has the ability to unpackage digests. .sp .I Vn supports the -n, -x and -t options of .I readnews (newsgroup, read all articles, and title). In addition, there is a -w (writer) option which works like -t, but is a search string to apply to the "From" header line rather than the subject. In the -n, -t and -w options, a leading ! on the string is taken to mean negation. The rest of the string is a regular expression for the -w and -t options. .sp For example: .sp -n net.dogs -w !fred -t [Bb]eagle .sp For articles in net.dogs about beagles written by somebody other than fred. Multiple -w -t options result in the logical "or" of the -w's anded with the logical "or" of the -t's, whatever order you may specify them in. -n options allow the "all" convention, replacing ".all" by ".*" before using the regular expression calls. -n options are processed in order given, so that subsequent more specific -n's may partially undo the effect of previous "alls". .sp Options may be given on the command line, in which case they will supersede those given in the .B .newsrc file. For command line -n options, the "!" unsubscriptions in .B .newsrc are also ignored. This allows you to override all subscription information by command line specification. .sp When .I vn is invoked, there will be a pause (with an explanatory "reading" message and a series of newsgroups) while vn reads the news. The newsgroups listed are ones articles are actually being found in. The length of the pause depends on how much news there is. If there is a lot, it may take a long time to get through the reading phase. .sp Once the reading phase is over, interaction is rapid. If .I vn is backgrounded, it suppresses the "reading" output, so that it will not halt on tty output until it is ready to begin showing articles. .sp .I Vn may show you a list of newsgroups which were not mentioned in the .B .newsrc file. Records for these newsgroups will be added, whether they were scanned for articles or not. The first time .I vn is used, the list may be quite long and scroll off the screen. Thereafter, there should only be a list when new newsgroups are created. This display serves to let you know of their existence, or of something happening to your .B .newsrc file. .sp The basic display is a "page" which shows a newsgroup and a list of titles, number of lines, and authors for new articles. Articles which have been updated in the .B .newsrc file are flagged with an underscore preceding the article number. You also have the ability to "mark" articles for the duration of a session, shown with an asterisk (col. 1 and 2 are reserved for asterisk and underscore respectively - in normal usage they will be blank, so that the casual user will probably be unaware of their use until marking and updating are invoked) .sp There is a help menu to go with this page. You may read articles, save them, send them to the printer, either by cursor position, the whole page, or in specified sets. Sets are specified either as a set of article numbers, a regular expression to match the subject / author / number of lines data on, or an asterisk to indicate the choice of a set of previously marked articles. Any of these methods also accept a leading "!" to indicate negation. .sp By default, when you read articles only a couple of the dozen or so header lines are shown. There is an option to allow you to see all the header lines when you read articles. The command controlling this toggles between the two states. .sp A similar toggle is used to support ROT13. .sp .I Vn is capable of manipulating digests. The "d" command unpacks a digest, and presents you with a page showing the unpacked articles, which can be accessed as for articles on normal newsgroup pages. When you leave the digest page(s), you reenter the normal flow of newsgroups. Digests can also be read as normal articles, of course. .sp Order of pages is determined by order of groups in .B .newsrc. Newsgroups which are not mentioned in .B .newsrc will be added, as mentioned previously, and tacked onto the end. Lines corresponding to non-existent newsgroups will be deleted. You will probably want to run .I vn once, then edit .B .newsrc to the desired order of presentation. .sp Updating the data for .B .newsrc is under user control. If you do no "W", "w", "^w", o or O commands, no updating takes place, and you'll see the articles again the next time you read news. Breaks result in a "really quit?" query, so you can recover from noisy lines and prompts for commands you didn't really mean. If you quit without updating, you will be prompted to make sure you don't want to do so. .sp Commands are single character (no return key required), except that they may be preceded with numeric characters, which may have some effect on their actions. Commands which require further input cause prompts for the information, this input being terminated by return. For prompted input, the erase and kill keys work. .sp .ce 1 Command Menu For Page: .sp .nf [...] = effect of optional number preceding command pipes are specified by filenames beginning with | articles specified as a list of numbers, title search string, or * to specify marked articles. ! may be used to negate any q - quit k - move up [number of lines] j - move down [number of lines] - previous page [number of pages] - next page [number of pages] d - unpack digest r - read article [number of articles] - read article (alternate 'r') R - read all articles on page control-r - specify articles to read s - save or pipe article [number of articles] S - save or pipe all articles on page control-s - specify articles to save control-t - specify articles to save (alternate ctl-s) p - print article [number of articles] P - print all article on page control-p - specify articles to print w - update .newsrc status to cursor W - update .newsrc status for whole newsgroup control-w - update .newsrc status for all pages displayed o - recover original .newsrc status for newsgroup O - recover all original .newsrc status # - display count of groups and pages - shown and total % - list newsgroups with new article, updated counts n - specify newsgroup to display and/or resubscribe to u - unsubscribe from group x - mark/unmark article [number of articles] * - mark/unmark article [number of articles] X - erase marks on articles h - toggle flag for display of headers when reading z - toggle rotation for reading - redraw screen ! - escape to UNIX to execute a command ? - show this help menu .fi .sp When you read articles, there is another help menu, for advancing through the articles, replying, posting followups, and saving the articles. Breaks may be used to stop the output of an article if you decide that you didn't really want to read it. You can jump from the reading portion back to either page you came from or the NEXT page. .sp For replying and posting followups, you will be thrown into an editor to create the reply or article. The article will be included in the file you are editing, marked with "> "'s for excerpting in your reply or followup. After you exit the editor, you are prompted to make sure you still want to post or reply, so you can abort. .sp For the "mail reply" choice, you will be shown the address taken from the article, and you may specify a different one. This is done with the article you have been reading still on the screen so that you may copy the authors suggested path, if present. .sp The editor is determined by your EDITOR variable, as for .I postnews. If EDITOR is not set, you get .I vi, or the default determined at your site. .sp .ce 1 Reading menu: .sp .nf n - next article, if any q - quit reading articles, if any more to read Q - quit reading, and turn to next page of articles r - rewind article to beginning - next line m - send mail to author of article f - post followup to article s - save article in a file ? - see this help menu z - toggle rotation flag h - toggle header suppression flag anything else to continue normal reading .fi .sp .SH FILES .TP 24 /usr/tmp/* One temporary file created by .I tmpnam (3), and immediately unlinked, remains open in update mode for duration of session. Disk space freed by system close of file descriptor at exit. Can be large, as this file contains the "page" displays. Temporary files also created by .I tmpnam (3) for mailing replies, posting followups and creating digest "articles". .TP 24 (login directory)/.newsrc news status file. Updated following session. See NEWSRC environment variable. .TP 24 (login directory)/*.vn One temporary file created by .I tmpnam (3) while updating the .newsrc file. If the update fails, you are informed, and this file may be used to recover the last update. Unlinked following successful update. .TP 24 (spool directory)/* spooling directories containing articles. .TP 24 /usr/lib/news/active active newsgroup list. .SH "ENVIRONMENT VARIABLES" .TP 24 PS1 used to present prompt string for command on unix escape. defaults to "$ " .TP 24 EDITOR editor used for mailing replies and posting followups. defaults to "ed". .TP 24 POSTER posting program for followups. defaults to "inews -h". .TP 24 MAILER used when mailing replies. defaults to "/bin/mail". .TP 24 PRINTER program used with the print commands for sending articles to the printer. defaults to "lpr -p". .TP 24 NEWSRC if set, can be used to override the choice of ".newsrc" as the name for the status file. Name will still be used relative to the login directory. .SH DIAGNOSTICS user error messages. self explanatory. .SH AUTHOR R. L. McQueer .SH BUGS Note that .I readnews will rearrange the order of .B .newsrc. If you interleave use of it with .I vn, order selection gets hosed. .sp If you've really taken advantage of the ability of readnews to skip articles in the middle of the spooling numbers, be warned that .I vn doesn't have it, and will assume you've read the articles in the middle. .sp If the .B .newsrc file indicates that you've read articles in a newsgroup with a higher number than the current spooling number for that newsgroup, .I vn will show you the entire newsgroup. This is intended for recovery in cases where article spooling has been reset, or to avoid missing articles because you just changed machines and didn't bother to edit your .B .newsrc file. Rather than miss stuff, you'll see some old stuff again. .sp Sometimes a "break" during reading an article will not only halt the article but suppress the prompt. A command character will work anyway. .sp If a prompt to be displayed on the dialogue line contains non-printing sequences, stuff on the line may not get erased when you are prompted, because .I vn thinks the string is long enough to overprint its current contents. This usually comes up when you have escape sequences in your UNIX prompt, and do a "!" command. The "overprint" check is made to save a clear-line sequence (kludged in by overprinting to the end with blanks if the terminal doesn't have one - annoying at 1200 baud). .sp Output during the reading phase which was suppressed by backgrounding .I vn does not get started by foregrounding it again without doing a control-z and a second foreground (it doesn't figure out its background / foreground status on each output - only on startup and while handling the SIGTSTP signal). Actually, this results in a method for having .I vn do its reading phase silently in the foreground without redirecting output, should such a thing be desired. .sp Very many -w or -t options cause SLOW reading phases. It is reccomended that these be used only when reading a few specific groups. .sp Digest extraction will split a single article into several if it contains embedded ---- lines, the normal separator between articles in digests. They will all have identical titles. Digest extraction may not work with human built digests which don't use the expected syntax for joining articles. mod.computers.ibm-pc and mod.computers.mac were used as models for the feature. SHAR_EOF echo shar: extracting "'mf.nore'" '(229 characters)' if test -f 'mf.nore' then echo shar: over-writing existing file "'mf.nore'" fi cat << \SHAR_EOF > 'mf.nore' CFLAGS= -O LIBS= -ltermcap OBJS= hash.o groupdir.o envir_set.o newsrc.o pagefile.o reader.o storage.o sig_set.o term_set.o tty_set.o userlist.o vn.o vnglob.o digest.o strings.o ucb.o reg.o vn: $(OBJS) cc -o vn $(OBJS) $(LIBS) SHAR_EOF echo shar: extracting "'mf.re'" '(256 characters)' if test -f 'mf.re' then echo shar: over-writing existing file "'mf.re'" fi cat << \SHAR_EOF > 'mf.re' CFLAGS= -O -Dregfree=free REGLIB= LIBS= -ltermcap OBJS= hash.o groupdir.o envir_set.o newsrc.o pagefile.o reader.o storage.o sig_set.o term_set.o tty_set.o userlist.o vn.o vnglob.o digest.o strings.o ucb.o vn: $(OBJS) cc -o vn $(OBJS) $(REGLIB) $(LIBS) SHAR_EOF echo shar: extracting "'config.h'" '(745 characters)' if test -f 'config.h' then echo shar: over-writing existing file "'config.h'" fi cat << \SHAR_EOF > 'config.h' /* ** HASHMAX is in effect the number of newsgroups allowed. ** HASHMAX MUST be less than HASHSIZE. ** HASHSIZE should be prime - size of hash table newsgroups ** are entered in, linear probe to resolve collisions ** (HASHMAX / HASHSIZE = max. density). */ #define HASHMAX 500 #define HASHSIZE 809 #define DEF_ED "/usr/ucb/vi" /* editor to use if no EDITOR variable */ #define DEF_PS1 "$ " /* ! command prompt if no PS1 */ #define DEF_SAVE "vn.save" /* save file */ #define DEF_MAIL "/bin/mail" /* mailer */ #define DEF_PRINT "/usr/ucb/lpr" /* print command */ #define DEF_POST "/usr/lib/news/inews -h" /* followup posting command */ #define DEF_NEWSRC ".newsrc" #define SPOOLDIR "/usr/spool/news" #define ACTFILE "/usr/lib/news/active" SHAR_EOF echo shar: extracting "'head.h'" '(564 characters)' if test -f 'head.h' then echo shar: over-writing existing file "'head.h'" fi cat << \SHAR_EOF > 'head.h' /* header lines and associated lengths. Strings should actually be used once in strings.c */ #define RHEAD "References: " #define RHDLEN 12 #define MHEAD "Message-ID: " #define MHDLEN 12 #define PHEAD "Path: " #define PHDLEN 6 #define DHEAD "Date: " #define DHDLEN 6 #define FHEAD "From: " #define FHDLEN 6 #define FTHEAD "Followup-To: " #define FTHDLEN 13 #define THEAD "Subject: " #define THDLEN 9 #define LHEAD "Lines: " #define LHDLEN 7 #define NHEAD "Newsgroups: " #define NHDLEN 12 #define CHFIRST "FSL" /* first char's of those used in page display */ SHAR_EOF echo shar: extracting "'reader.h'" '(1174 characters)' if test -f 'reader.h' then echo shar: over-writing existing file "'reader.h'" fi cat << \SHAR_EOF > 'reader.h' #define PAGE_MID ":more (%2d%%):" #define PAGE_NEXT ":next article:" #define PAGE_END ":end:" #define PAGE_NO ":?:" #define PPR_MAX 18 /* maximum length of PAGE prompts */ /* reading commands: no control chars, add help message to helppg SAVE, HEADTOG and SETROT are also recognized */ #define HPG_HEAD "toggle header print flag" #define HPG_ROT "toggle rotation" #define HPG_SAVE "save article in a file" #define PG_NEXT 'n' #define HPG_NEXT "next article, if any" #define PG_QUIT 'q' #define HPG_QUIT "quit reading articles, if any more to read" #define PG_FLIP 'Q' #define HPG_FLIP "quit reading, and turn to next page of articles" #define PG_FOLLOW 'f' #define HPG_FOLLOW "post followup to article" #define PG_REPLY 'm' #define HPG_REPLY "send mail to author of article" #define PG_HELP '?' #define HPG_HELP "see this help menu" #define PG_REWIND 'r' #define HPG_REWIND "rewind article to beginning" #define PG_WIND 'e' #define HPG_WIND "seek to end of article (to next/end prompt)" #define PG_STEP '\n' #define HPG_STEP "next line" #define HPG_DEF "\n anything else to continue normal reading" #define HPG_EDEF "\n anything else to try reading next article, if any" SHAR_EOF echo shar: extracting "'tty.h'" '(286 characters)' if test -f 'tty.h' then echo shar: over-writing existing file "'tty.h'" fi cat << \SHAR_EOF > 'tty.h' /* term_set and tty_set codes */ #define MOVE 100 #define ERASE 101 #define START 102 #define STOP 103 #define RUBSEQ 104 #define ZAP 105 #define ONREVERSE 106 #define OFFREVERSE 107 #define RAWMODE 200 #define COOKED 201 #define SAVEMODE 202 #define RESTORE 203 #define BACKSTOP 204 SHAR_EOF echo shar: extracting "'tune.h'" '(2173 characters)' if test -f 'tune.h' then echo shar: over-writing existing file "'tune.h'" fi cat << \SHAR_EOF > 'tune.h' /* ** buffer size needed for tmpnam() */ #ifndef L_tmpnam #define L_tmpnam 48 #endif /* ** maximum number of columns on terminal. If made smaller, there ** will be a savings in the size of the temporary file used ** for holding displays, at the penalty of not being able to use ** the entire screen width on terminals actually possessing more ** columns than this. A block roughly on the order of this value ** times the number of lines the terminal has is maintained in ** the temp file, and read / written as displays are interacted ** with. MIN_C put here because MAX_C > MIN_C. MIN_C is the minumum ** number of columns for which a "reasonable" display can be produced. ** before making it smaller, look at all uses of C_allow and variable ** to see that a setting that small won't screw up array bounds. */ #define MAX_C 132 #define MIN_C 36 /* ** large size for general purpose local buffers. only used in automatic ** variable declarations. Used with fgets for buffer size when reading ** file records, to hold pathnames, commands, etc. Reduce if you blow ** out stack storage. If reduced too far, will eventually show up ** as syntax errors on reading .newsrc's and the active list, and ** scrozzled article information arising from truncated header lines. ** The reply path line will probably be the first thing to cause trouble. ** Look through the reader to find the worst case chain of declarations ** (on the order of 12 or so is probably the max). */ #define RECLEN 1200 /* ** to protect against reading entire articles to find non-existent header ** lines if an article should be hosed, only a limited number of records ** are searched. Should be big enough to get down to the "Lines" header ** entry on legitimate articles. */ #define HDR_LINES 18 /* records of article to search for header line */ /* these determine some static array sizes */ #define OPTLINES 60 /* maximum number of option lines in .newsrc */ #define NUMFILTER 24 /* max number of filters on articles */ /* block sizes for allocation routines */ #define STRBLKSIZE 1800 /* string storage allocation block */ #define NDBLKSIZE 50 /* NODE structures to allocate at a time */ SHAR_EOF echo shar: extracting "'vn.h'" '(4193 characters)' if test -f 'vn.h' then echo shar: over-writing existing file "'vn.h'" fi cat << \SHAR_EOF > 'vn.h' #include "tune.h" #define TRUE 1 #define FALSE 0 #define NARGOPT "lprxfuMs" #define FIL_AUTHOR 'w' #define FIL_TITLE 't' /* newsrc states */ #define NEWS_ON ':' #define NEWS_OFF '!' /* bit flags for state of newsgroup */ #define FLG_SCAN 1 #define FLG_SUB 2 #define FLG_PAGE 4 #define FLG_WRIT 8 #define FLG_SPEC 16 #define LIST_SEP " ," #define ED_MARK '>' #define ART_MARK '*' #define ART_WRITTEN '_' #define ART_UNWRITTEN ' ' #define FPFIX "Re: " #define ANFORM ":%s - ? for help:\n" #define ANFLINES 1 #define NOFORM "can't open article %s\n" #define NEWGFORM "groups not mentioned in %s:\n" #define SAVFORM "save file (%s) ? " #define UDKFORM "undefined key (x%x) - ? for help" /* page display format and dependent parameters */ #define HFORMAT "\n%s (page %d of %d):" #define DHFORMAT "\n%s (DIGEST EXTRACTION):" #define TFORMAT "%s ~ %s %s" #define AFORMAT "\n%c%c%d) " /* begin with newline - see show routine */ #define CFORMAT "page %d of %d (%d shown), newsgroup %d of %d" #define RECBIAS 2 /* lines before articles - depends on HFORMAT */ #define AFLEN 5 /* min. char. in article id - depends on AFORMAT */ #define WRCOL 1 /* column of written mark. depends on AFORMAT */ #define INFOLINE 0 /* HFORMAT TFORMAT leaves for use */ /* command characters - don't use numerics ALTSAVE is a hack to avoid having to use ctl-s - XON/XOFF. Wanted to preserve "s" pneumonic and lower / control /cap convention. */ #define DIGEST 'd' #define UP 'k' #define DOWN 'j' #define FORWARD '\012' #define BACK '\010' #define READ 'r' #define ALTREAD ' ' #define READALL 'R' #define READSTRING '\022' #define SAVE 's' #define SAVEALL 'S' #define SAVESTRING '\023' #define ALTSAVE '\024' #define PRINT 'p' #define PRINTALL 'P' #define PRINTSTRING '\020' #define MARK 'x' #define UNMARK 'X' #define REDRAW '\014' #define QUIT 'q' #define SSTAT '#' #define GRPLIST '%' #define ORGGRP 'o' #define ORGSTAT 'O' #define UPDATE 'w' #define UNSUBSCRIBE 'u' #define UPALL 'W' #define UPSEEN '\027' #define UNESC '!' #define NEWGROUP 'n' #define HEADTOG 'h' #define SETROT 'z' #define HELP '?' #define HELP_HEAD "[...] = effect of optional number preceding command\n\ pipes are specified by filenames beginning with |\n\ articles specified as a list of numbers, title search string, or\n\ * to specify marked articles. ! may be used to negate any\n" #define HHLINES 5 /* lines (CRs + 1) contained in HELP_HEAD */ /* state flags for handling breaks / values for sig_set calls. BRK_IN, BRK_SESS, BRK_READ and BRK_OUT are the states. All but BRK_INIT are used as calls to sig_set. BRK_RFIN indicates a return from BRK_READ to BRK_SESS (no jump location passed), */ #define BRK_INIT 0 /* initial value, indicating uncaught signals */ #define BRK_IN 1 /* in NEWSRC / article scanning phase */ #define BRK_SESS 2 /* in page interactive session */ #define BRK_READ 3 /* reading articles */ #define BRK_RFIN 4 /* finished reading, return to old mode */ #define BRK_OUT 5 /* NEWSRC updating phase */ #define BRK_PR "really quit ? " #define BRK_MSG "\nQUIT (signal %d)" /* filter structure for article screening: rex - regular expression hcomp - header line to compare against regular expression neg - negation flag indicating "not matching" */ typedef struct { char *rex,hcomp,neg; } FILTER; /* newsgroup structure (node of hash table) nd_name - name of newsgroup (key to reach node by) pnum - page number pages - number of pages for news display rdnum - articles read orgrd - original articles read number pgshwn - pages shown mask pgrd - article number on highest conecutively shown page art - articles in group state - status */ typedef struct { char *nd_name; int pnum,pages,art,rdnum,orgrd,pgrd; unsigned long pgshwn; unsigned state; } NODE; /* newsgroup information for page display name - of group group - pointer to table entry artnum - number of articles */ typedef struct { char *name; NODE *group; int artnum; } HEAD; /* article information - id (spool) number, title string, mark, written. */ typedef struct { int art_id; char art_mark; char art_written; char art_t[MAX_C-AFLEN]; } BODY; typedef struct { HEAD h; BODY *b; } PAGE; SHAR_EOF # End of shell archive exit 0 From bobm@rtech.UUCP (Bob Mcqueer) Wed May 21 14:26:25 1986 Path: seismo!lll-crg!lll-lcc!unisoft!mtxinu!rtech!bobm From: bobm@rtech.UUCP (Bob Mcqueer) Newsgroups: net.sources Subject: vn (Part 2 of 3) Message-ID: <268@rtech.UUCP> Date: 21 May 86 18:26:25 GMT Distribution: net Organization: Relational Technology Inc, Alameda CA Lines: 1709 cut here -------------------------------- #!/bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #!/bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # vn.c # reader.c # This archive created: Tue May 20 12:52:10 1986 export PATH; PATH=/bin:$PATH echo shar: extracting "'vn.c'" '(20696 characters)' if test -f 'vn.c' then echo shar: over-writing existing file "'vn.c'" fi cat << \SHAR_EOF > 'vn.c' /* vn news reader for visual page oriented display of news aimed at scanning large numbers of articles. Known bugs: non-erasure of stuff on prompt line when the new string includes an escape sequence (like PS1 maybe) because it doesn't realize that the escape sequence won't overprint the existing stuff control-w may not update pages which have been scanned in funny orders by jumping into the middle of groups R. L. McQueer, Author */ #include #include #include "tty.h" #include "vn.h" /* UNIX error number */ extern int errno; extern NODE *Newsorder []; extern char Erasekey, Killkey; extern int Rot; extern char *Ps1,*Printer; extern char *Orgdir,*Savefile,*Savedir; extern int Ncount, Cur_page, Lrec, L_allow, C_allow; extern int Headflag; extern PAGE Page; extern int Digest; extern char *No_msg; extern char *Help_msg; extern char *Hdon_msg; extern char *Hdoff_msg; extern char *Roton_msg; extern char *Rotoff_msg; extern char *Aformat; extern char *Contstr; static int C_info; static int Dskip, Drec; static char *Unsub_msg = "Unsubscribed"; static char *Egroup_msg = "Entire newsgroup"; /* Help message table. Character for command, plus its help message. Table order is order of presentation to user. */ static struct HELPTAB { char cmd, *msg; int dig; char *amsg; } Helptab [] = { { QUIT, "quit", 1, NULL}, { UP, "move up [number of lines]", 1, NULL}, { DOWN, "move down [number of lines]", 1, NULL}, { BACK, "previous page [number of pages]", 1, NULL}, { FORWARD, "next page [number of pages]", 1, NULL}, { DIGEST, "unpack digest", 1, "exit digest"}, { READ, "read article [number of articles]", 1, NULL}, { ALTREAD, "read article (alternate 'r')", 1, NULL}, { READALL, "read all articles on page", 1, NULL}, { READSTRING, "specify articles to read", 1, NULL}, { SAVE, "save or pipe article [number of articles]", 1, NULL}, { SAVEALL, "save or pipe all articles on page", 1, NULL}, { SAVESTRING, "specify articles to save", 1, NULL}, { ALTSAVE, "specify articles to save (alternate ctl-s)", 1, NULL}, { PRINT, "print article [number of articles]", 1, NULL}, { PRINTALL, "print all article on page", 1, NULL}, { PRINTSTRING, "specify articles to print", 1, NULL}, { UPDATE, "update .newsrc status to cursor", 0, NULL}, { UPALL, "update .newsrc status for whole newsgroup", 0, NULL}, { UPSEEN, "update .newsrc status for all pages displayed", 0, NULL}, { ORGGRP, "recover original .newsrc status for newsgroup", 0, NULL}, { ORGSTAT, "recover all original .newsrc status", 0, NULL}, { SSTAT, "display count of groups and pages - shown and total", 0, NULL}, { GRPLIST, "list newsgroups with new article, updated counts", 0, NULL}, { NEWGROUP, "specify newsgroup to display and/or resubscribe to", 1, NULL}, { UNSUBSCRIBE, "unsubscribe from group", 0, NULL}, { MARK, "mark/unmark article [number of articles]", 1, NULL}, { ART_MARK, "mark/unmark article [number of articles]", 1, NULL}, { UNMARK, "erase marks on articles", 1, NULL}, { HEADTOG, "toggle flag for display of headers when reading", 1, NULL}, { SETROT, "toggle rotation for reading", 1, NULL}, { REDRAW, "redraw screen", 1, NULL}, { UNESC, "escape to UNIX to execute a command", 1, NULL}, { HELP, "show this help menu", 1, NULL} }; main(argc,argv) int argc; char **argv; { /* initialize environment variables, scan .newsrc file, using any command line options present. */ term_set (START); envir_set (); sig_set (BRK_IN); scan_newsrc (argc-1,argv+1); tty_set (BACKSTOP); if (Lrec >= 0) session (); else { new_groups (); fprintf (stderr,"\nNo News\n"); } tty_set (COOKED); wr_newsrc (); term_set (STOP); } /* main session handler processing input commands locals: count - count attached to command highrec - highest line on current page crec - current line NOTE: this is where a setjmp call is made to set the break reentry location. Keep the possible user states in mind. */ session () { char alist [RECLEN], c; int newg, i, j, count, highrec, crec; jmp_buf brkbuf; tty_set (RAWMODE); newg = new_groups(); find_page (0); Digest = 0; /* reentry point for break from within session interaction */ setjmp (brkbuf); sig_set (BRK_SESS,brkbuf); Headflag = FALSE; Rot = 0; /* done this way so that user gets "really quit?" break treatment */ if (newg > 0) { printf ("\n%s",Contstr); getnoctl(); newg = 0; } /* if breaking from a digest, recover original page */ if (Digest) { find_page(Cur_page); Digest = 0; } show (); crec = RECBIAS; highrec = Page.h.artnum + RECBIAS; term_set (MOVE,0,crec); /* handle commands until QUIT, update global/local status and display for each. */ for (count = getkey(&c); c != QUIT; count = getkey(&c)) { for (i=0; i < (j = sizeof(Helptab)/sizeof(struct HELPTAB)); ++i) if (Helptab[i].cmd == c) break; if (i >= j || (Digest && !Helptab[i].dig)) { preinfo (UDKFORM,(int) c); term_set (MOVE, 0, crec); continue; } switch (c) { case HEADTOG: if (Headflag) { Headflag = FALSE; prinfo (Hdoff_msg); } else { Headflag = TRUE; prinfo (Hdon_msg); } term_set (MOVE,0,crec); break; case SETROT: if (Rot == 0) { Rot = 13; prinfo (Roton_msg); } else { Rot = 0; prinfo (Rotoff_msg); } term_set (MOVE,0,crec); break; case SSTAT: count_msg (); term_set (MOVE,0,crec); break; case GRPLIST: tot_list (); show(); term_set (MOVE,0,crec); break; case REDRAW: show(); term_set (MOVE,0,crec); break; case UNSUBSCRIBE: (Page.h.group)->state &= ~FLG_SUB; wr_newsrc (); prinfo (Unsub_msg); term_set (MOVE,0,crec); break; case UPDATE: (Page.h.group)->rdnum = Page.b[crec-RECBIAS].art_id; wr_show (); wr_newsrc(); term_set (MOVE,0,crec); break; case UPALL: (Page.h.group)->rdnum = (Page.h.group)->art; wr_newsrc(); wr_show(); prinfo (Egroup_msg); term_set (MOVE,0,crec); break; case ORGGRP: (Page.h.group)->rdnum = (Page.h.group)->orgrd; wr_newsrc(); wr_show(); prinfo (Egroup_msg); term_set (MOVE,0,crec); break; case UPSEEN: for (i = 0; i < Ncount; ++i) if ((Newsorder[i])->rdnum < (Newsorder[i])->pgrd) (Newsorder[i])->rdnum = (Newsorder[i])->pgrd; prinfo ("All pages displayed to this point updated"); wr_show(); wr_newsrc(); term_set (MOVE,0,crec); break; case ORGSTAT: for (i = 0; i < Ncount; ++i) (Newsorder[i])->rdnum = (Newsorder[i])->orgrd; prinfo ("Original data recovered"); wr_show(); wr_newsrc(); term_set (MOVE,0,crec); break; case UP: if (crec != RECBIAS) { crec -= count; if (crec < RECBIAS) crec = RECBIAS; term_set (MOVE, 0, crec); } else putchar ('\07'); break; case DOWN: if (crec < (highrec - 1)) { crec += count; if (crec >= highrec) crec = highrec - 1; term_set (MOVE, 0, crec); } else putchar ('\07'); break; case MARK: case ART_MARK: count += crec - 1; if (count >= highrec) count = highrec - 1; for (i=crec; i <= count; ++i) { if (Page.b[i-RECBIAS].art_mark != ART_MARK) Page.b[i-RECBIAS].art_mark = ART_MARK; else Page.b[i-RECBIAS].art_mark = ' '; if (i != crec) term_set (MOVE, 0, i); printf ("%c\010",Page.b[i-RECBIAS].art_mark); } if (count != crec) term_set (MOVE, 0, crec); write_page (); break; case UNMARK: for (i=0; i < Page.h.artnum; ++i) { if (Page.b[i].art_mark == ART_MARK) { Page.b[i].art_mark = ' '; term_set (MOVE, 0, i+RECBIAS); putchar (' '); } } term_set (MOVE, 0, crec); write_page (); break; case BACK: count *= -1; /* fall through */ case FORWARD: if (forward (count, &crec, &highrec) >= 0) show(); else preinfo ("No more pages"); term_set (MOVE,0,crec); break; case DIGEST: if (Digest) { Digest = 0; find_page (Cur_page); show(); crec = Drec + RECBIAS + 1; highrec = Page.h.artnum + RECBIAS; if (crec >= highrec) crec = highrec - 1; term_set (MOVE,0,crec); break; } Dskip = count - 1; Drec = crec - RECBIAS; if (digest_page(Drec,Dskip) >= 0) { show(); crec = RECBIAS; highrec = Page.h.artnum + RECBIAS; term_set (MOVE,0,crec); break; } Digest = 0; preinfo ("Can't unpack the article"); term_set (MOVE,0,crec); break; case NEWGROUP: if ((i = spec_group()) < 0) { term_set (MOVE,0,crec); break; } Digest = 0; show(); crec = RECBIAS; highrec = Page.h.artnum + RECBIAS; term_set (MOVE,0,crec); break; case SAVE: genlist (alist,crec-RECBIAS,count); savestr (alist); term_set (MOVE,0,crec); break; case SAVEALL: genlist (alist,0,L_allow); savestr (alist); term_set (MOVE,0,crec); break; case SAVESTRING: case ALTSAVE: userlist (alist); savestr (alist); term_set (MOVE,0,crec); break; case READ: case ALTREAD: genlist (alist,crec-RECBIAS,count); readstr (alist,&crec,&highrec,count); break; case READALL: genlist (alist,0,L_allow); readstr (alist,&crec,&highrec,0); break; case READSTRING: userlist (alist); readstr (alist,&crec,&highrec,0); break; case PRINT: genlist (alist,crec-RECBIAS,count); printstr (alist); term_set (MOVE,0,crec); break; case PRINTALL: genlist (alist,0,L_allow); printstr (alist); term_set (MOVE, 0, crec); break; case PRINTSTRING: userlist (alist); printstr (alist); term_set (MOVE, 0, crec); break; case HELP: help (); show (); term_set (MOVE, 0, crec); break; case UNESC: user_str (alist,Ps1); term_set (ERASE); fflush (stdout); tty_set (SAVEMODE); if (chdir(Orgdir) < 0) printf ("change to original directory, %s, failed",Orgdir); else system (alist); tty_set (RESTORE); printf (Contstr); getnoctl (); cd_group (); show (); term_set (MOVE, 0, crec); break; default: printex ("Unhandled key: %c", c); break; } } Digest = 0; for (i=0; i < Ncount; ++i) { if ((Newsorder[i])->rdnum < (Newsorder[i])->pgrd) break; } if (i < Ncount) { user_str (alist,"Some displayed pages not updated - update ? "); if (alist[0] == 'y') { for (i=0; irdnum < (Newsorder[i])->pgrd) (Newsorder[i])->rdnum = (Newsorder[i])->pgrd; } } } sig_set (BRK_OUT); } /* count_msg displays count information */ count_msg () { int i, gpnum, gscan, gpage; unsigned long mask; gpnum = 1; for (gscan = gpage = i = 0; istate & FLG_PAGE) != 0) { if (((Newsorder[i])->pnum + (Newsorder[i])->pages - 1) < Cur_page) ++gpnum; ++gpage; for (mask=1; mask != 0L; mask <<= 1) if (((Newsorder[i])->pgshwn & mask) != 0L) ++gscan; } } prinfo (CFORMAT,Cur_page+1,Lrec+1,gscan,gpnum,gpage); } /* forward utility handles paging to allow it to happen globally. (from readstr, for instance) */ forward (count, crec, highrec) int count, *crec, *highrec; { if (!Digest) { if ((count < 0 && Cur_page <= 0) || (count > 0 && Cur_page >= Lrec)) return (-1); Cur_page += count; if (Cur_page < 0) Cur_page = 0; if (Cur_page > Lrec) Cur_page = Lrec; find_page (Cur_page); *crec = RECBIAS; *highrec = Page.h.artnum + RECBIAS; return (0); } /* ** in digests, paging past the end of the digest returns to ** page extracted from. */ if (Dskip > 0 && (Dskip + count*L_allow) < 0) Dskip = 0; else Dskip += count * L_allow; find_page (Cur_page); if (Dskip >= 0) { if (digest_page(Drec,Dskip) >= 0) { *crec = RECBIAS; *highrec = Page.h.artnum + RECBIAS; return (0); } } Digest = 0; *crec = Drec + RECBIAS + 1; *highrec = Page.h.artnum + RECBIAS; if (*crec >= *highrec) *crec = *highrec - 1; return (0); } /* error/abnormal condition cleanup and abort routine pass stack to printf */ printex (s,a,b,c,d,e,f) char *s; long a,b,c,d,e,f; { static int topflag=0; if (topflag == 0) { ++topflag; term_set (STOP); tty_set (COOKED); fflush (stdout); fprintf (stderr,s,a,b,c,d,e,f); fprintf (stderr," (error code %d)\n",errno); exit (1); } else fprintf (stderr,s,a,b,c,d,e,f); } /* getkey obtains user keystroke with count from leading numerics, if any. */ getkey (c) char *c; { int i; for (i = 0; (*c = getchar() & 0x7f) >= '0' && *c <= '9'; i = i * 10 + *c - '0') ; /* @#$!!! flakey front ends that won't map newlines in raw mode */ if (*c == '\012' || *c == '\015') *c = '\n'; if (i <= 0) i = 1; return (i); } /* get user key ignoring most controls */ getnoctl () { char c; while ((c = getchar() & 0x7f) < ' ' || c == '\177') { if (c == '\015' || c == '\012' || c == '\b' || c == '\t') return (c); } return ((int) c); } /* generate list of articles on current page, count articles, starting with first. */ genlist (list,first,count) char *list; int first,count; { int i; for (i=first; i < Page.h.artnum && count > 0; ++i) { sprintf (list,"%d ",Page.b[i].art_id); list += strlen(list); --count; } } /* send list of articles to printer */ printstr (s) char *s; { char *ptr, cmd [RECLEN], *strpbrk(); prinfo ("preparing print command ...."); for (ptr = s; (ptr = strpbrk(ptr, LIST_SEP)) != NULL; ++ptr) *ptr = ' '; while (*s == ' ') ++s; if (Digest) dig_list (s); if (*s != '\0') { sprintf (cmd,"%s %s 2>/dev/null",Printer,s); if (system (cmd) == 0) prinfo ("Sent to printer"); else preinfo ("Print failed"); } else preinfo (No_msg); if (Digest) dig_ulist (s); } /* concatenate articles to save file with appropriate infoline messages. prompt for save file, giving default. If save file begins with "|" handle as a filter to pipe to. NOTE - every user specification of a new Savefile "loses" some storage, but it shouldn't be a very great amount. */ savestr (s) char *s; { char *ptr, cmd [RECLEN], newfile [MAX_C+1], prompt[MAX_C]; char *strtok(), *strpbrk(), *str_store(); for (ptr = s; (ptr = strpbrk(ptr, LIST_SEP)) != NULL; ++ptr) *ptr = ' '; while (*s == ' ') ++s; if (Digest) dig_list (s); if (*s != '\0') { sprintf (prompt,SAVFORM,Savefile); user_str (newfile,prompt); ptr = newfile; if (*ptr == '|') { sprintf(cmd,"cat %s %s",s,ptr); term_set (ERASE); fflush (stdout); tty_set (SAVEMODE); system (cmd); tty_set (RESTORE); printf (Contstr); getnoctl (); show (); } else { prinfo ("saving .... "); if (*ptr == '\0') ptr = Savefile; else Savefile = str_store(ptr); if (*ptr != '/' && *ptr != '$') sprintf(cmd,"cat %s >>%s/%s 2>/dev/null",s,Savedir,ptr); else sprintf(cmd,"cat %s >>%s 2>/dev/null",s,ptr); if (system (cmd) == 0) prinfo ("Saved"); else preinfo ("Could not append save file"); } } else preinfo (No_msg); if (Digest) dig_ulist (s); } /* basic page display routine. erase screen and format current page */ show () { int i; unsigned long mask; term_set (ERASE); C_info = 0; i = Cur_page - (Page.h.group)->pnum + 1; if (Digest) printf (DHFORMAT,Page.h.name); else printf (HFORMAT,Page.h.name,i,(Page.h.group)->pages); mask = 1L << (i-1); (Page.h.group)->pgshwn |= mask; mask = 1; for (--i; i > 0 && (mask & (Page.h.group)->pgshwn) != 0 ; --i) mask <<= 1; if (i <= 0) (Page.h.group)->pgrd = Page.b[(Page.h.artnum)-1].art_id; for (i=0; i < Page.h.artnum; ++i) { if (Digest) { printf(Aformat,Page.b[i].art_mark,ART_UNWRITTEN,Page.b[i].art_id); printf("%s",Page.b[i].art_t); continue; } if ((Page.h.group)->rdnum >= Page.b[i].art_id) printf(Aformat,Page.b[i].art_mark,ART_WRITTEN,Page.b[i].art_id); else printf(Aformat,Page.b[i].art_mark,ART_UNWRITTEN,Page.b[i].art_id); printf("%s",Page.b[i].art_t); } if (!Digest && ((Page.h.group)->state & FLG_SUB) == 0) prinfo ("%s, %s",Unsub_msg,Help_msg); else prinfo (Help_msg); } /* update written status marks on screen */ wr_show () { int i,row; char c; row = RECBIAS; for (i=0; i < Page.h.artnum; ++i) { term_set (MOVE,WRCOL,row); if ((Page.h.group)->rdnum >= Page.b[i].art_id) c = ART_WRITTEN; else c = ART_UNWRITTEN; printf("%c",c); ++row; } } /* obtain user input of group name, becomes current page if valid. returns -1 or page number. calling routine does the show, if needed */ spec_group () { char nbuf [MAX_C + 1]; NODE *p, *hashfind(); user_str (nbuf,"Newsgroup ? "); if (*nbuf == '\0' || (p = hashfind(nbuf)) == NULL) { preinfo ("Not a newsgroup"); return (-1); } if ((p->state & FLG_PAGE) == 0) { if ((p->state & FLG_SUB) == 0) { p->state |= FLG_SUB; wr_newsrc (); prinfo ("Not subscribed: resubscribed for next reading session"); } else prinfo ("No news for that group"); return (-1); } if ((p->state & FLG_SUB) == 0) { p->state |= FLG_SUB; wr_newsrc (); } find_page (p->pnum); return (p->pnum); } /* obtain user input with prompt p on the info line. handle erase and kill characters, suppresses leading white space. */ user_str (s,p) char *s; char *p; { int i; prinfo ("%s",p); for (i=0; C_info < C_allow && (s[i] = getchar() & 0x7f) != '\012' && s[i] != '\015'; ++i) { if (s[i] == Erasekey) { if (i > 0) { term_set (RUBSEQ); i -= 2; --C_info; } continue; } if (s[i] == Killkey) { prinfo ("%s",p); i = -1; continue; } if ((s[i] == ' ' || s[i] == '\t') && i == 0) { i = -1; continue; } ++C_info; putchar (s[i]); } s[i] = '\0'; } /* print something on the information line, clearing any characters not overprinted. */ preinfo (s,a,b,c,d,e,f) { int l; char buf[RECLEN]; term_set (MOVE,0,INFOLINE); putchar ('\07'); term_set (ONREVERSE); sprintf (buf,s,a,b,c,d,e,f); printf (" %s ",buf); term_set (OFFREVERSE); l = strlen(buf) + 2; if (l < C_info) term_set (ZAP,l,C_info); C_info = l; } prinfo (s,a,b,c,d,e,f) char *s; long a,b,c,d,e,f; { int l; char buf[RECLEN]; term_set (MOVE,0,INFOLINE); sprintf (buf,s,a,b,c,d,e,f); printf ("%s",buf); l = strlen(buf); if (l < C_info) term_set (ZAP,l,C_info); C_info = l; } /* help menu */ help () { int i,lcount,lim; term_set (ERASE); lim = L_allow + RECBIAS - 2; printf("%s\n",HELP_HEAD); lcount = HHLINES; for (i=0; i < (sizeof(Helptab))/(sizeof(struct HELPTAB)); ++i) { if (Digest && !(Helptab[i].dig)) continue; ++lcount; if (Digest && Helptab[i].amsg != NULL) h_print (Helptab[i].cmd,Helptab[i].amsg); else h_print (Helptab[i].cmd,Helptab[i].msg); if (lcount >= lim) { printf ("\n%s",Contstr); getnoctl (); term_set (MOVE,0,lim+1); term_set (ZAP,0,strlen(Contstr)); term_set (MOVE,0,lim-1); putchar ('\n'); lcount = 0; } } if (lcount > 0) { printf ("\n%s",Contstr); getnoctl (); } } /* h_print prints a character and a legend for a help menu. */ h_print(c,s) char c,*s; { if (strlen(s) > (C_allow - 14)) s [C_allow - 14] = '\0'; if (c > ' ' && c != '\177') printf (" %c - %s\n",c,s); else { switch (c) { case '\177': printf (" - %s\n",s); break; case '\040': printf (" - %s\n",s); break; case '\033': printf (" - %s\n",s); break; case '\n': printf (" - %s\n",s); break; case '\t': printf (" - %s\n",s); break; case '\b': printf (" - %s\n",s); break; case '\f': printf (" - %s\n",s); break; case '\07': printf (" - %s\n",s); break; case '\0': printf (" - %s\n",s); break; default: if (c < '\033') { c += 'a' - 1; printf(" control-%c - %s\n",c,s); } else printf(" %c0%o - %s\n",'\\',(int) c,s); break; } } } tot_list () { int i,max,len; char ff[80]; term_set (ERASE); for (max=i=0; i < Ncount; ++i) { if ((Newsorder[i])->pages == 0) continue; if ((len = strlen((Newsorder[i])->nd_name)) > max) max = len; } sprintf (ff,"%%%ds: %%3d new %%3d updated\n",max); for (len=i=0; i < Ncount; ++i) { if ((Newsorder[i])->pages == 0) continue; printf (ff, (Newsorder[i])->nd_name, (Newsorder[i])->art - (Newsorder[i])->orgrd, (Newsorder[i])->rdnum - (Newsorder[i])->orgrd); ++len; if (len == L_allow && i < (Ncount-1)) { printf("\nq to quit, anything else to continue ... "); if (getnoctl() == 'q') break; printf ("\n\n"); len = 0; } } if (i >= Ncount) { printf(Contstr); getnoctl(); } } SHAR_EOF echo shar: extracting "'reader.c'" '(13632 characters)' if test -f 'reader.c' then echo shar: over-writing existing file "'reader.c'" fi cat << \SHAR_EOF > 'reader.c' #include #include "tty.h" #include "vn.h" #include "head.h" #include "reader.h" #define PERTAB 8 /* tab expansion factor */ #define BACKTRACK 24 extern char *Editor,*Mailer,*Poster,*Orgdir,*Savefile,*Savedir; extern int L_allow; extern int C_allow; extern int Rot; extern int Headflag; extern int Digest; extern char *No_msg; extern char *Roton_msg; extern char *Rotoff_msg; extern char *Hdon_msg; extern char *Hdoff_msg; extern char *T_head, *FT_head, *N_head, *L_head; extern char *F_head, *P_head, *M_head, *R_head; static FILE *Fpread; static char *Fname; /* readstr routine is the "funnel" to the reading state, and controls signal setting. Some "session" context is passed along to allow jumping back to a different display WARNING: NOTHING below readstr should call strtok() */ readstr (s,crec,highrec,count) char *s; int *crec, *highrec; int count; { char *fnext, *strtok(); int pc; Fname = strtok(s,LIST_SEP); if (Fname != NULL) { term_set (ERASE); sig_set (BRK_READ,&Fpread); fnext = strtok(NULL,LIST_SEP); while (Fname != NULL && readfile(fnext,&pc) >= 0) { if (Digest) unlink (Fname); Fname = fnext; fnext = strtok (NULL,LIST_SEP); } if (Digest && Fname != NULL) unlink (Fname); if (pc != 0) forward (pc, crec, highrec); else { *crec += count; if (*crec >= *highrec) *crec = *highrec - 1; } sig_set (BRK_RFIN); show (); term_set (MOVE, 0, *crec); } else { preinfo ("%s",No_msg); term_set (MOVE, 0, *crec); } } /* readfile presents article: sn - name of next article, NULL if last. pages - pages to advance on return, if applicable returns 0 for "continue", <0 for "quit" */ static readfile (sn,pages) char *sn; int *pages; { FILE *fopen(); int lines,hlines,rlines,percent,artlin; long rew_pos, ftell(); float total,lcount; double atof(); char *optr; char c, *rf, buf[RECLEN], path[RECLEN], mid[RECLEN], ngrp[RECLEN]; char from[RECLEN], title[RECLEN], flto[RECLEN], pstr[24]; char dgname[48], getpgch(), *index(), *digest_extract(); char *tgetstr(); *pages = 0; term_set(ERASE); if (Digest) { lines = atoi(Fname); if ((Fname = digest_extract(dgname,lines)) == NULL) { printf ("couldn't extract article %d",lines); return (0); } } if ((Fpread = fopen(Fname,"r")) == NULL) { printf ("couldn't open article %s",Fname); return (0); } hlines = gethead (path, mid, from, title, ngrp, flto, &artlin); total = (float) artlin + 1.0; if (total < .99) total = 1.0; lcount = 0.0; printf (ANFORM,Fname); lines = 1; rlines = 0; rew_pos = ftell(Fpread); if (Headflag) { rewind(Fpread); } else { /* use do_out to guard against control sequences */ sprintf (buf,"%s%s\n",T_head,title); lines += do_out(buf,&optr,1); sprintf (buf,"%s%s\n",F_head,from); lines += do_out(buf,&optr,1); if (index(ngrp,',') != NULL) { sprintf (buf,"%s%s\n",N_head,ngrp); lines += do_out(buf,&optr,1); } if (*flto != '\0') { sprintf (buf,"%s%s\n",FT_head,flto); lines += do_out(buf,&optr,1); } printf ("%s%d\n",L_head,artlin); /* no controls */ ++lines; } /* will return out of outer while loop */ optr = NULL; while (1) { /* ** lines counts folded lines from do_out. ** hlines, rlines and lcount refer to records. */ rf = buf; lines += do_out(optr,&optr,L_allow-lines); while (lines < L_allow && (rf = fgets(buf,RECLEN-1,Fpread)) != NULL) { lcount += 1.0; if (Rot != 0) { if (!Headflag || rlines >= hlines) rot_line(buf); ++rlines; } lines += do_out(buf,&optr,L_allow-lines); } if (rf != NULL) { if (Headflag) percent = 100.0*(lcount/(total+hlines)) + .5; else percent = 100.0*(lcount/total) + .5; sprintf (pstr,PAGE_MID,percent); } else { if (sn == NULL) strcpy (pstr,PAGE_END); else strcpy (pstr,PAGE_NEXT); } c = getpgch(pstr,path,mid,from,title,ngrp,flto); /* handle user input: CAUTION!! return cases must close Fpread. */ switch (c) { case PG_NEXT: fclose (Fpread); return (0); case PG_FLIP: *pages = 1; /* fall through */ case PG_QUIT: fclose (Fpread); return (-1); case PG_REWIND: if (Headflag) rewind (Fpread); else fseek (Fpread,rew_pos,0); lcount = 0.0; rlines = 0; lines = 2 - RECBIAS; break; case PG_WIND: fseek (Fpread,0L,2); lines = 2 - RECBIAS; break; case PG_STEP: if (rf == NULL) { fclose (Fpread); return (0); } lines = L_allow - 1; break; default: if (rf == NULL) { fclose (Fpread); return (0); } lines = 2 - RECBIAS; break; } } } /* gethead obtains subject, path, message id, from, lines, newsgroup and followup-to lines of article for later use in mailing replies and posting followups, does not rewind, but leaves file at end of header lines. Returns number of header lines. */ static gethead (path, mid, from, title, ngrp, flto, lin) char *path, *mid, *from, *title, *ngrp, *flto; int *lin; { int count; char buf [RECLEN],*index(); long pos,ftell(); *lin = 0; *path = *mid = *from = *title = *ngrp = *flto = '\0'; /* for conditional is abnormal - expected exit is break */ for (count = 0; count < HDR_LINES && fgets(buf,RECLEN-1,Fpread) != NULL; ++count) { /* reset position and bail out at first non-header line */ if (index(buf,':') == NULL) { pos = ftell(Fpread); pos -= strlen(buf); fseek (Fpread,pos,0); break; } if (strncmp(buf,P_head,PHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; strcpy (path,buf+PHDLEN); continue; } if (strncmp(buf,M_head,MHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; strcpy (mid,buf+MHDLEN); continue; } if (strncmp(buf,F_head,FHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; strcpy (from,buf+FHDLEN); continue; } if (strncmp(buf,T_head,THDLEN) == 0) { buf [strlen(buf)-1] = '\0'; strcpy (title,buf+THDLEN); continue; } if (strncmp(buf,N_head,NHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; strcpy (ngrp,buf+NHDLEN); continue; } if (strncmp(buf,FT_head,FTHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; strcpy (flto,buf+FTHDLEN); continue; } if (strncmp(buf,L_head,LHDLEN) == 0) { buf [strlen(buf)-1] = '\0'; *lin = atoi(buf+LHDLEN); continue; } } return (count); } /* getpgch prints prompt and gets command from user handles "mail", "save" and "followup" internally as well as flag resets. */ static char getpgch(prompt,path,mid,from,title,ngrp,flto) char *prompt, *path, *mid, *from, *title, *ngrp, *flto; { char c; int ic; term_set (ONREVERSE); printf("%s\015",prompt); term_set (OFFREVERSE); while ((ic=getnoctl()) != EOF) { switch (c = ic) { case SETROT: term_set (ZAP,0,PPR_MAX); if (Rot == 0) { Rot = 13; printf ("%s\n",Roton_msg); } else { Rot = 0; printf ("%s\n",Rotoff_msg); } break; case HEADTOG: term_set (ZAP,0,PPR_MAX); if (Headflag) { Headflag = FALSE; printf ("%s\n",Hdoff_msg); } else { Headflag = TRUE; printf ("%s\n",Hdon_msg); } break; case PG_HELP: term_set (ZAP,0,PPR_MAX); help_pg (); break; case PG_REPLY: mail (path,title,from); break; case PG_FOLLOW: followup (mid,title,ngrp,flto); break; case SAVE: saver (); break; default: term_set (ZAP,0,PPR_MAX); return (c); } term_set (ONREVERSE); printf("%s\015",prompt); term_set (OFFREVERSE); } term_set (ZAP,0,PPR_MAX); return (c); } /* save article Like the savestr routine, it "loses" some storage every time the user specifies a new file, but this should not be significant */ static saver () { char *fn,cmd[RECLEN],*str_store(),*rprompt(); tty_set (SAVEMODE); sprintf (cmd,SAVFORM,Savefile); fn = rprompt(cmd,cmd); if (fn != NULL) Savefile = str_store(fn); if (*Savefile != '/' && *Savefile != '$') sprintf (cmd,"cat %s >>%s/%s",Fname,Savedir,Savefile); else sprintf (cmd,"cat %s >>%s",Fname,Savefile); system (cmd); tty_set (RESTORE); } /* invoke editor on new temp file, mail using path line, first allowing user to overide the path if wished. */ static mail (p, t, f) char *p, *t, *f; { char *new, fn[L_tmpnam], cmd [RECLEN+60], *rprompt (); FILE *fp, *fopen(); tmpnam (fn); if ((fp = fopen(fn,"w")) == NULL) printex ("can't open %s\n",fn); fprintf (fp,"%s%s%s\n\n%s:\n", T_head, FPFIX, t, f); edcopy (fp); fclose (fp); tty_set (SAVEMODE); sprintf (cmd,"ADDRESS: %s\nreturn to accept, or input new address: ",p); if ((new = rprompt(cmd,cmd)) != NULL) strcpy (p,new); sprintf (cmd,"%s %s", Editor, fn); chdir (Orgdir); system (cmd); cd_group (); new = rprompt ("still want to mail it ? ",cmd); if (new != NULL && (*new == 'y' || *new == 'Y')) { sprintf (cmd,"%s %s <%s", Mailer, p, fn); system (cmd); printf ("given to mailer\n"); } else printf ("not mailed\n"); unlink (fn); tty_set (RESTORE); } /* post a followup article, invoking editor for user after creating new temp file. remove after posting. Hack in ".followup" if posting newsgroup ends in ".general" - should really be more thorough and parse the whole string. User can change, anyway. */ static followup (m,t,n,ft) char *m, *t, *n, *ft; { char fn[L_tmpnam], *new, cmd [RECLEN], *rprompt(); FILE *f, *fopen(); char *rindex(); tmpnam (fn); if ((f = fopen(fn,"w")) == NULL) printex ("can't open %s\n",fn); if (*ft != '\0') strcpy (cmd,ft); else strcpy (cmd,n); if ((new = rindex(cmd,'.')) != NULL && strcmp(new,".general") == 0) strcpy (new,".followup"); fprintf (f,"%s%s%s\n%s%s\n%s%s\n",T_head,FPFIX,t,N_head,cmd,R_head,m); edcopy (f); fclose (f); tty_set (SAVEMODE); sprintf (cmd,"%s %s", Editor, fn); chdir (Orgdir); system (cmd); cd_group (); new = rprompt("still want to post it ? ",cmd); if (new != NULL && (*new == 'y' || *new == 'Y')) { sprintf (cmd,"%s <%s", Poster, fn); system (cmd); printf ("given to posting program\n"); } else printf ("not posted\n"); unlink (fn); tty_set (RESTORE); } /* get user buffer, return whitespace delimited token without using strtok(). buffer is allowed to overwrite prompt string. */ char *rprompt(s,buf) char *s,*buf; { printf("%s",s); fgets (buf,RECLEN-1,stdin); while (*buf == ' ' || *buf == '\t') ++buf; if (*buf == '\n' || *buf == '\0') return (NULL); for (s = buf; *s != ' ' && *s != '\t' && *s != '\n' && *s != '\0'; ++s) ; *s = '\0'; return (buf); } /* edcopy copies article to file which user is editting for a reply or followup, so it may be referenced. It places ED_MARK in the left hand margin. */ edcopy(fp) FILE *fp; { long current; char buf[RECLEN]; int i; /* save position, rewind and skip over header lines */ current = ftell(Fpread); rewind (Fpread); for (i=0; i < HDR_LINES; ++i) { if (fgets(buf,RECLEN-1,Fpread) == NULL) break; if (strncmp(buf,L_head,LHDLEN) == 0) break; } /* if line already begins with ED_MARK, forget about the space */ while (fgets(buf,RECLEN-1,Fpread) != NULL) { if (buf[0] == ED_MARK) fprintf(fp,"%c%s",ED_MARK,buf); else fprintf(fp,"%c %s",ED_MARK,buf); } /* restore position */ fseek(Fpread,current,0); } /* help menus */ static help_pg() { h_print (PG_NEXT,HPG_NEXT); h_print (PG_QUIT,HPG_QUIT); h_print (PG_FLIP,HPG_FLIP); h_print (PG_REWIND,HPG_REWIND); h_print (PG_WIND,HPG_WIND); h_print (PG_STEP,HPG_STEP); h_print (PG_REPLY,HPG_REPLY); h_print (PG_FOLLOW,HPG_FOLLOW); h_print (SAVE,HPG_SAVE); h_print (SETROT,HPG_ROT); h_print (HEADTOG,HPG_HEAD); h_print (PG_HELP,HPG_HELP); printf ("%s\n",HPG_DEF); } rot_line (s) unsigned char *s; { for ( ; *s != '\0'; ++s) { if (*s >= 'A' && *s <= 'Z') { *s += Rot; if (*s > 'Z') *s -= 26; continue; } if (*s >= 'a' && *s <= 'z') { *s += Rot; if (*s > 'z') *s -= 26; } } } /* ** output record. folds record to terminal width on word boundaries, ** returning number of lines output. If limit is reached, remainder ** of buffer waiting to be output is returned. Processes several ** special characters: ** form-feed - return "lim" lines so we stop on that line ** tabs - counts "expanded" width ** backspace - assumes they work, -1 width unless in first col. ** bell - pass through with zero width ** newline - end of record. ** del - turns into '_' ** other control - 'A' - 1 added ('01' = ctl-A). Makes escape = "[". ** (prevents "letter bombs" containing inappropriate control ** sequences for the terminal). */ static do_out(s,rest,lim) char *s; char **rest; int lim; { int len,i,j; char cs,*word,*start; *rest = NULL; if (s == NULL) return(0); len = 0; start = word = s; /* ** NOTE: "normal" return is buried inside switch, at newline ** ending record */ for (i=0; i < lim; ++i) { for ( ; len < C_allow; ++s) { switch (*s) { case '\n': *s = '\0'; /* fall through */ case '\0': printf("%s\n",start); return(i+1); case '\t': len = ((len/PERTAB)+1)*PERTAB; word = s; break; case '\b': if (len > 0) --len; break; case '\014': *s = ' '; i = lim-1; /* fall through */ case ' ': word = s+1; ++len; break; case '\177': *s = '_'; ++len; break; default: if (*s < ' ') *s += 'A' - 1; ++len; /* fall through */ case '\07': break; } } cs = *s; *s = '\0'; if ((len = strlen(word)) < BACKTRACK) { *s = cs; s = word; cs = *s; *s = '\0'; } else len = 0; printf("%s\n",start); start = s; *s = cs; } *rest = start; return(lim); } SHAR_EOF # End of shell archive exit 0