home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Phoenix CD 2.0
/
Phoenix_CD.cdr
/
01e
/
more_ddj.zip
/
MORE.C
< prev
next >
Wrap
Text File
|
1986-09-27
|
18KB
|
701 lines
/* BROWSE.C 26-Sep-86 28415 Accesses: 5
Keywords: HOLUB OCT86 C FILE BROWSING UTILITY BROWSE C CHEST
A file-browsing utility by Allen Holub (October 1986 C-Chest column).
Listing 1 -- more.c Listing 2 -- b_getc.c
--28413 bytes
Listing 1 -- more.c
---------------------------------------------------------------------- */
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <process.h>
/* MORE.C Page input to stdout.
*
* (C) 1986, Allen I. Holub. All rights reserved.
*
* Usage: more [-<offset>] file...
*
* Exit status: 0 always
*/
/*-----------------------------------------------------------------*/
extern int b_getc (); /* Direct console input function */
/* source in /src/tools/b_getc.c */
extern int look (); /* Char. lookahead function. */
/* source in /src/tools/look.asm */
extern long ftell ( FILE *); /* Standard library functions: */
extern long atol ( char* );
extern FILE *fopen ( char*, char* );
extern int fclose ( FILE* );
extern int spawnl ( int, char*, char*, );
extern char *getenv ( char* );
extern char *fgets ( char*, int, FILE* );
extern long filelength ( int );
/*-----------------------------------------------------------------*/
#define CAN 0x18 /* ^X */
#define ESC 0x1b /* ^[ */
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
#define BSIZE 256 /* Maximum length of a line in the file */
#define PAGESIZE 23 /* # of lines to output before stopping */
#define E(x) fprintf(stderr,"%s\n", x)
#define HAS_DOT(p) strchr(p,'.')
FILE *Ifile = stdin; /* Current input file */
char *Ifile_name = "/dev/con"; /* Name of file associated w/ Ifile */
int Repeat_count = 1; /* Repeat count for most recent cmd */
long Line = 0; /* # of output Lines printed so far */
long Flen = 0; /* Length of input file in chars */
long Start_here = 0; /* Seek to here when prog starts */
/*-----------------------------------------------------------------
* Stack used to keep track of start of lines. Maximum number
* of lines is determined by STACKSIZE.
*/
typedef long STACKTYPE;
#define STACKSIZE (1024*6) /* Must be divisible by 2 */
STACKTYPE Stack[ STACKSIZE ];
STACKTYPE *Sp = Stack + STACKSIZE;
#define STACKFULL (Sp <= Stack)
#define STACKEMPTY (Sp >= Stack + STACKSIZE )
#define CLEAR_STACK() Sp = Stack + STACKSIZE ;
#define TOS (STACKEMPTY ? 0 : *Sp)
#define BACK_SCRN *( min( Sp+(PAGESIZE-1), Stack+(STACKSIZE-1)) )
#define erase_line() line( ' ', 0 ) /* Draw a line of spaces */
/*------------------------------------------------------------------*/
help()
{
register int i;
/* Print a help message with a box around it, special IBM graphics
* characters are used for the box
*/
putc( 0xd6, stderr );
for( i = 56 ; --i >= 0; putc( 0xc4, stderr) )
;
E("\267");
E("\272 b ............... go (B)ack a page \272");
E("\272 e ............... go to end of file \272");
E("\272 n ............... go to (N)ext file \272");
E("\272 o ............... print (O)ffset from start of file \272");
E("\272 q ............... (Q)uit (return to DOS) \272");
E("\272 s ............... (S)kip one line (w/o printing) \272");
E("\272 r ............... (R)ewind file (go back to beginning) \272");
E("\272 ! ............... execute a program (type blank line \272");
E("\272 at prompt to execute last) \272");
E("\272 / ............... search for regular expression \272");
E("\272 (type blank line at prompt for last) \272");
E("\272 ESC ............. Scroll until any key is hit \272");
E("\272 CR .............. next line \272");
E("\272 SP .............. next screen \272");
E("\272 anything else ... print this list \272");
E("\272 \272");
E("\272 All commands may be preceeded by a count. \272");
putc( 0xd3, stderr );
for( i = 56 ; --i >= 0; putc( 0xc4 ,stderr) )
;
E("\275");
}
/*------------------------------------------------------------------*/
usage()
{
E("more: Copyright (C) 1986, Allen I. Holub. All rights reserved.");
E("\nUseage: more [+<num>] [file...] \n");
E("Print all files in list on the screen, pausing every 23 lines");
E("If + is specified, more will start printing at character <num>");
E("One of the following commands is executed after each page:");
help();
exit(1);
}
/*----------------------------------------------------------------------*/
push( file_posn )
long file_posn; /* Push file_posn onto the stack */
{
if( STACKFULL ) /* If the stack is full, compress it */
comp_stk();
*( --Sp ) = file_posn;
}
/*------------------------------------------------------------------*/
long pop()
{
/* Pop one entry off the stack and return the file
* position.
*/
return STACKEMPTY ? 0 : *Sp++ ;
}
/*------------------------------------------------------------------*/
comp_stk()
{
/* Compress the stack by removing every other entry.
* This routine is called when the stack is full (we've
* read more lines than the stack can hold).
*/
register STACKTYPE *dest, *src;
fprintf(stderr,"\007Stack Full: Compressing\n");
src = dest = Stack + STACKSIZE;
while( (src -= 2) >= Stack )
*--dest = *src;
Sp = dest;
}
/*------------------------------------------------------------------*/
getcon()
{
/* Get one character from the console using a direct
* bios call. Map \r into \n if one is encountered.
*/
register int c;
c = b_getc() & 0x7f; /* Get a character from console */
putchar(c); /* Echo character */
return ( c == '\r' ) ? '\n' : c ;
}
/*----------------------------------------------------------------------*/
clear_io()
{
/* Clears the entire I/O queue, both at the bios and the
* bdos level.
*/
while( look() )
b_getc();
#ifdef NEVER
while( kbhit() )
getchar();
#endif
}
/*----------------------------------------------------------------------*/
khit() /* Return true if a key has been hit on */
{ /* the physical keyboard (as compared */
if( look() ) /* with a character available on stdin) */
{
clear_io();
return 1;
}
return 0;
}
/*----------------------------------------------------------------------*/
char *getbuf( p )
register char *p;
{
/* Get a line of input using direct console I/O and put it
* into buf. Return a pointer to the first whitespace on the
* line or to end of line if none. This routine is for
* getting commands from the user, not for getting normal
* input. ^H is supported as a destructive backspace but no
* other editing is available.
*/
register int c;
int gottail = 0;
char *start = p;
char *tail = "" ;
clear_io();
while( (c=getcon()) != '\n' )
{
if( c == '\b' )
{
if( p <= start )
fputs( " \007", stderr );
else
{
--p;
fputs( " \b", stderr );
}
}
else
{
if( isspace(c) && !gottail )
gottail = (int)( tail = p );
*p++ = c;
}
}
*p = '\0';
return( p <= start ? NULL : tail );
}
/*------------------------------------------------------------------*/
percent(s)
char *s;
{
/* Print the percentage of the file that we've seen so far */
printf("%4.1f%%%s", ((double)TOS / (double)Flen) * 100.00, s );
}
/*------------------------------------------------------------------*/
int getcmd()
{
/* Get a command from the keyboard, using direct
* bios I/O. Commands take the form [num]<c>. Returns
* the command. Repeat_count is initialized to hold [num]
* or 1 if no num is entered.
*/
int c;
clear_io();
percent("");
printf(", line %ld (? for commands): ", Line );
Repeat_count = 0;
while( '0' <= (c = getcon()) && c <= '9' )
Repeat_count = (Repeat_count * 10) + (c - '0');
if( Repeat_count == 0 )
Repeat_count = 1;
erase_line();
if( c == 0x03 ) /* ^C == abort */
exit( 1 );
return( c );
}
/*------------------------------------------------------------------*/
char *inputline( suppress )
{
/* Get a line from the file being processed and put it into
* buf. Push the start of line character onto the stack.
* return 0 on end of file, a pointer to the line (ie. to buf)
* otherwise.
*/
register int rval;
register long start_of_line ;
static char buf[BSIZE];
start_of_line = ftell( Ifile );
if( rval = (int) fgets(buf, BSIZE, Ifile) )
{
Line++;
push( start_of_line );
if( !suppress )
fputs( buf, stdout );
}
return rval ? buf : NULL ;
}
/*------------------------------------------------------------------*/
printpage()
{
/* Print an entire page from the input file */
register int i;
for( i = PAGESIZE-1; --i >= 0 && inputline(0); )
;
}
/*-------------------------------------------------------------------*/
search()
{
/* Prompt for a pattern and then search for it in the
* file. Stop searching if the pattern is found or if
* any key is hit. The previous pattern is remembered
* in a local array so, if CR is entered instead of a
* pattern, the previous pattern is used.
*/
static char pat[128], opat[128] ;
char *iline;
extern int *makepat();
int *template;
printf("/");
if( !getbuf( pat ) )
strcpy( pat, opat );
if( !(template = makepat( pat, 0 )) )
printf("Illegal regular expression: %s\n", pat );
else
{
erase_line();
printf("/%s\n", pat );
while( (iline = inputline(1)) && !khit() )
{
percent("\r");
if( matchs( iline, template, 0) )
break;
}
unmakepat( template );
fseek( Ifile, pop(), 0 ); /* back up one line */
--Line;
line ( 0xcd, 1);
printpage();
}
strcpy( opat, pat );
}
/*----------------------------------------------------------------------*/
execute()
{
/* Spawn off a child process. When the process terminates
* print a message and redraw the current page. Note that
* spawn() is used (rather than system()) so you can't
* execute a batch file or a built-in command. This
* subroutine will set the CMDLINE environment variable
* to a null string for the sake of those routines that
* are executing under the shell which will use it.
*/
static char buf[128];
char *tail = " ";
static char obuf[128], *otail = obuf;
register char *p;
register int c;
int got_tail = 0;
printf("!");
if( !(tail = getbuf(buf)) ) /* If no command entered, */
{ /* use the same one we */
tail = otail; /* used last time */
memcpy( buf, obuf, 128 );
printf( "\n!%s %s\n", buf, tail );
}
else
{
if( *tail )
*tail++ = '\0';
}
if( HAS_DOT(buf) )
{
/* Spawnlp will actually try to execute any file that you
* give it. If you say to execute an ASCII file, it will
* load that file into memory, try to execute it, and die
* a horrible death. We attempt to avoid this by checking
* for a dot in the file name. You may want to put a
* more rigorous test here.
*/
fprintf(stderr,"\007<%s> is not a command\n", buf);
}
else
{
putenv("CMDLINE=");
if( spawnlp(P_WAIT, buf, buf, tail, NULL) == -1)
fprintf(stderr,"Can't execute <%s %s>\n", buf, tail );
}
printf("Hit any key to continue ....");
getcon();
erase_line();
putchar('\n');
otail = tail;
memcpy( obuf, buf, 128 );
}
/*----------------------------------------------------------------------*/
line( c , newline )
{
/* Print a line of characters to mark top of page. 0xcd
* is the IBM graphics character for a horizontal double
* line. The cursor is put at the beginning of next line
* if "newline" is true, else it's put at beginning of
* current line.
*/
register int i;
putchar('\r');
for( i = 79; --i >= 0 ; putchar( c ) )
;
putchar( newline ? '\n' : '\r' );
}
/*------------------------------------------------------------------*/
backapage( count )
{
/* Go back count pages and print the resulting page.
*/
register int i;
i = ((count+1) * PAGESIZE) -1;
while( --i >= 0 )
{
Line--;
pop();
}
line ( 0xcd, 1);
fseek( Ifile, pop(), 0 );
Line = max( Line - 1, 0 );
printpage();
}
/*------------------------------------------------------------------*/
docmd( cmd, ateof )
{
/* Do a single command, return 1 if next file is requested.
* Actually call exit on a "quit" command or ^C.
*/
register int rval = 0;
register int i;
long posn;
do {
switch( cmd )
{
case CAN: break; /* NOP */
case 'q': exit(0); /* abort */
case '\n': /* FORWARD MOTION */
if( ateof ) /* one line */
rval = 1;
else
inputline(0);
break;
case ' ': /* one page */
if( ateof )
rval = 1;
printpage();
break;
case 'e': /* To end of file */
erase_line();
while( inputline(1) && !khit() )
percent("\r");
break;
case 's': /* one line w/o printing */
if( ateof )
rval = 1;
else
{
erase_line();
inputline(1);
percent("\r");
}
break;
case ESC: /* scroll till key is hit */
if( ateof )
rval = 1;
else
while( inputline(0) && !khit() )
clear_io();
clear_io();
Repeat_count = 0; /* Ignore repeat count */
break; /* if it's set */
case 'n': /* to next file */
rval = 1;
break;
case '/': /* search for pattern */
search();
break;
case 'r': /* to start of file */
line( 0xcd, 1 );
CLEAR_STACK();
Line = 0;
fseek( Ifile, 0L, 0 );
printpage();
break;
case 'b': /* to previous page */
backapage( Repeat_count );
Repeat_count = 0;
break;
case 'o': /* print file position */
printf("Top line = %ld, ", BACK_SCRN );
printf("Bottom line = %ld\n", TOS );
break;
case '!':
/* Close the file and spawn another shell.
* when we come back, reopen the file
* and position to the same place we
* were before. This is necessary because of
* a bug in Microsoft C ver. 3.0's spawn functions
* (they trash the IOB). It will cause problems
* if standard input is used as the input source
* (as in a pipe) because we won't be able to
* successfully reopen stdin.
*/
Repeat_count = 0; /* Ignore repeat count */
fclose( Ifile );
execute();
posn = pop();
if( Ifile = fopen(Ifile_name, "r") )
{
fseek( Ifile, posn, 0 );
backapage( 0 );
}
else
{
fprintf(stderr,"more: can't open %s\n",
Ifile_name);
rval = 1;
}
break;
default : /* Print the help msg. */
help();
cmd = getcmd(); /* get a new command */
Repeat_count++;
break;
}
} while( --Repeat_count > 0 );
return( rval );
}
/*----------------------------------------------------------------------*/
dofile( fname )
char *fname;
{
/* Process lines from an input file having the indicated
* name.
*/
if( (Ifile_name = fname) && !(Ifile = fopen(fname, "r")) )
fprintf(stderr, "more: can't open %s\n", fname );
else
{
Flen = filelength( fileno(Ifile) );
fseek( Ifile, Start_here, 0 );
CLEAR_STACK();
docmd(' ', 0 ); /* dump the first page */
for(;;)
{
for(;;)
{
if( docmd( getcmd(), 0) )
return;
if( feof(Ifile) )
break;
}
E("\n\020\020\020 LAST LINE IN FILE \021\021\021");
if( docmd( getcmd(), 1) )
break;
}
fclose( Ifile );
}
}
/*------------------------------------------------------------------*/
main(argc, argv)
char **argv;
{
ctlc();
reargv(&argc, &argv);
if( argc > 1 )
{
if( argv[1][0] == '-' )
usage();
else if (argv [1][0] == '+' )
{
Start_here = atol( &argv[1][1] );
printf("Starting at character %ld\n", Start_here );
push ( Start_here );
++argv;
--argc;
}
}
if( argc <= 1 )
dofile( NULL );
else
for(; --argc > 0 ; dofile(*++argv) )
;
exit(0);
}