[net.sources] A version of HELP for PC/MS-DOS.

scott@mosaic.dec.com (Robert P. Scott) (01/07/87)

Recently, a package called GNUPLOT was posted which required a VMS-like
help utility.  Unfortunately, the help utility provided for unix would not
work under MS-DOS/PC-DOS.  What follows is a first pass at a MS-DOS version
of this utility.   It may be compiled with MSC V4.0.  My apologies for:

    1. Not "ifdef'ing" this into the original code.  My code is too dirty
       in it's present state.
    2. Not "shar'ing" this posting.  I'm on a MS-DOS machine at the moment.
    3. Not including any documentation.  I have not written any.

Please note the problem of token size due to the filename size limitation of
MS-DOS.  This will require the user to rename the GNUPLOT help files slightly
to work.  When I have time to fully test (read as debug) my version using 
lookup tables, I will post it.   

Also note that you must include an appropriate help file definition in the
plot.h file.  What I use is:

#define HELP  "\\usr\\bin\\help gnuplot"

The following file is 'help.h':
======================= Beginning of HELP.H ==============================
/*
 *    help.h: header file for the VMS-help emulator
 */
#define HELPVER         "HELP - VMS-like help utility\nVersion 1.0.014NL  -  6 January 1986\nRobert P. Scott\nDTS Engineering & Consulting\n29 Heritage Circle\nHudson, NH  03051-3427\n(603)886-1383\n"

#define STDDELIM        " \r\n\t;"          /* delimiters for parsing */
#define HELPMAIN        "HELP.HLM"          /* main help text filename */
#define MAXENT          128                 /* maximum number of help entries*/
#define HELPEX          ".HLP"              /* help extension */
#define SWITCHR1        '-'                 /* unix like switch char */
#define SWITCHR2        '/'                 /* PC/MS-DOS std switch char */
#define PROMPT          "Topic? "
#define DEFSRCH1        ".\\*.*"             /* Default search string */
#define R_OK            04
#define MAXLINELEN      90
#define MAXNAMELEN      12                  /* max length of NAME */
#define COLUMNSPACE     4
#define TERMWID         76
#define HELPDIR         "/usr/help"
#define ALL             0x37
#define DEFSCRLEN       17

int         scrnlen;
int         status;
int         second;                     /* first time indicator for allsel */
char        dent[MAXENT][MAXNAMELEN];
char        lent1[MAXENT][MAXLINELEN];
char        lent2[MAXENT][MAXLINELEN];
char        progname[MAXNAMELEN+4];
char        olddir[MAXLINELEN];
char        newdir[MAXLINELEN];
char        currdir[MAXLINELEN];
char        prompt[MAXLINELEN];
char        *helpdir;

/*global*/  int help(char *);
/*global*/  int outhelp(char *);
/*global*/  int allsel(void);
/*global*/  void set_dta(char *);
/*global*/  int dir_get(char *,int );
/*global*/  char *gettok(char *,char *,char *,char *);
/*global*/  int isdelim(char ,char *);
/*global*/  int more(char *);
/*global*/  int pgbrk(void);
/*global*/  void stripnl(char *);
/*global*/  void setprompt(void);
/*global*/  void fstobs(char *);
/*global*/  void handler(void);
/*global*/  int kget(void);
======================= END of HELP.H ====================================

The following file is 'help.c':
======================= Beginning of HELP.C ==============================
#include <stdio.h>
#include <io.h>
#include <stdlib.h>
#include <conio.h>
#include <signal.h>
#include <ctype.h>
#include <direct.h>
#include <process.h>
#include <string.h>
#include <dos.h>
#include "help.h"
 
/*****************************************************************************
 *  HELP
 *
 *  This program provides a HELP environment similar to the VMS help facility.
 *  Instead of using a help library, however, this version uses a directory 
 *  tree of text files.  This was done primarily to allow easy editing and 
 *  addition of help entries.
 *
 *  The set of arguments passed from DOS is first scanned for switches.  Then 
 *  the remaining arguments are passed to the main help routine as a single
 *  string which is reparsed.  This was done to allow easy recursion.
 *
 *  Similar to the public domain help utility found on many UNIX systems, a 
 *  help directory contains five possible entries in it.
 *        main help text:          HELP.HLM
 *        manual page name:        HELP.MAN
 *        subtopic texts:          <topicname>.HLP
 *        subtopic directories:    <topicname>
 *
 *  Due to the filename size limitation of DOS, command line tokens are
 *  limited to eight characters.
 */
void main(argc, argv)
int argc;
char *argv[];
    {
    char        inbuf[MAXLINELEN];
    int         il;                         /* input buffer length */
    int         sl;                         /* token length */
    int         done;                       /* temporary flag */
    char        c;                          /* temporary char holder */
    char        *op;                        /* single argument pointer */
        
    strcpy( progname,*argv++ );         /* Always save the program name */

    second = 1;                         /* to tell allsel "first pass" */
    
    helpdir = getenv( "HELP" );         /* use EV for directory if set */    
    getcwd( olddir, MAXLINELEN );       /* save our startind directory */

    if ( signal( SIGINT, handler ) < 0 )    /* uh oh */
        {
        fprintf( stderr, "%s could not set SIGINT.  Action undefined upon INT\n", progname );
        }
        
    argc--;
    while (( argc ) && ( ( c = *(*argv) ) == SWITCHR1 ) || ( c == SWITCHR2 ))
        {        /* must be options */
        for (op = *argv; *op != '\0'; op++) 
            {
            switch(*op) 
                {
                case SWITCHR1 :    /* ignore first and extras */
                case SWITCHR2 :
                    break;
 
                case 'd' :    /* specifying help directory path explicitly */
                    helpdir = *++argv;
                    break;
 
                case 'h' :
                case 'v' :      /* show version number */
                    fprintf( stdout, HELPVER );
                    exit( 0 );
                    
                default:
                    fprintf(stderr,"%s: %c: bad option.\n",
                    progname,*op);
                    break;
                }   /* switch */
            }   /* for ( steps through token ) */
        argc--; argv++;
        }   /* while ( loops on argv arguments ) */
 
    if (helpdir == NULL)        /* if all else fails, use default */
        helpdir = HELPDIR;
 
    fstobs( helpdir );           /* normalize to dos directory seps */
    
    if (chdir(helpdir) < 0)     /* move to the help root */
        {
        fprintf(stderr,"%s: %s: help directory not found.\n",
            progname,helpdir);
        exit(1);
        }
 
    done = il = 0;                             /* reset input length */
    inbuf[0] = 0;                       /* start with a null buffer */
    while ( ( argc ) && ( *argv ) && !done )   /* concat. all remaining args */
        {
        sl = strlen( *argv );           /* new token length */
        if ( ( il + sl ) < MAXLINELEN ) /* if enough room for added token */
            {
            if ( il && ( il < MAXLINELEN ) )    /* if not the first token */
                {
                strcat( inbuf, " " );   /* add a space */
                strcat( inbuf, *argv ); /* and add the token */
                }
            else                        /* the first token */
                strcpy( inbuf, *argv ); /* copy in the first token */
            il += sl + 1;               /* adjust the inbuf length ctr */
            }
        else                            /* no more room for added tokens */
            done = 1;                   /* and we don't want junk added */            
        argv++;
        argc--;
        }

    strupr( inbuf );                    /* convert to upper case */    
    help( inbuf );
         
    chdir( olddir );                    /* go back to our origin */
    exit(0);
    }
 

/****************************************************************************
 */
help( buf )
char    *buf;
    {
    int     done = 0;
    int     subj = 0;
    char    lbuf[MAXLINELEN];           /* local temporary copy */
    char    tbuf[MAXLINELEN];           /* local token buffer */
    int     lbufl, bufl;                /* buffer lengths */
    
    while( !done )                      /* loop until user is tired */
        {
        if ( !buf || !*buf ) /* was input specified? */
            {
            if ( !subj )
                outhelp( NULL );
            subj = 0;                   /* no DEFAULT menu after subject */
            allsel();
            setprompt();
            fputs( prompt, stdout );        /* prompt user for input */
            if ( fgets( buf, MAXLINELEN, stdin ) == NULL )   /* EOF or error */
                return( -1 );
            }
        strupr( buf );                      /* convert to upper case */
        stripnl( buf );                     /* get rid of new line */
        if ( gettok( buf, STDDELIM, tbuf, lbuf ) == NULL ) /* no input token */
            return( 0 );

        strcpy( buf, lbuf );                /* for next pass */
        if ( tbuf[0] == '?' )           /* redisplay current main help */
            {
            }
        else
            {
/* go ahead and assume the input is a request for more help! */
            if ( ( status = outhelp( tbuf ) ) == -1 )
                {                       /* no help found, sorry */
                fprintf( stdout, "\nSorry, no help found on '%s'\n", tbuf );
                }
            else if ( status == 1 )
                {                       /* it was a subdir! */
                if ( ( status = help( buf ) ) < 0) /* parse further but... */
                    return( status );
                else                                /* otherwise, print main text */
                    {
                    chdir( ".." );              /* go back up for dos */
                    }
                }
            else
                subj = 1;
            }
        }    
    }


/****************************************************************************
 *  outhelp( str )      -   Output the given help file or ...
 *  char    *str;           A text file name, directory name, or NULL
 *
 *  If str is NULL, then print the HELP.HLM file in the current directory
 *  or append a .HLP to the str and print that file.
 */
outhelp( str )
char    *str;
    {
    char filename[MAXLINELEN];

    getcwd( currdir, MAXLINELEN );       /* save our startind directory */
        
    if ( !str || !*str ) /* if no input specified */
        strcpy( filename, HELPMAIN );       /* assume the default main file */
    else
        strcpy( filename, str );
        
    if ( chdir( filename ) == 0 )            /* see if it is a subdir by */
        {                                   /* trying to go to it */
        return( 1 );                        /* tell caller we are there */
        }
                
    strcat( currdir, "\\" );                 /* cause we gonna add toit */
    strcat( currdir, filename );
    
    if ( access(currdir, R_OK ) == 0 )     /* we got a valid file */
        {   
        more( filename );                   /* output the file */
        return( 0 );
        }
 
    strcat( currdir, HELPEX );              /* msdos kludge */
    strcat(filename,HELPEX);                /* add the default extension */
    
    if ( access( currdir, R_OK ) == 0 )     /* we got a valid file */
        {   
        more( filename );                   /* output the file */
        return( 0 );
        }
 
    return( -1 );                           /* not a help available */
    }
 

/****************************************************************************
 * allsel()                     print the topics available in this directory
 *
 *  This function prints a directory of the help files and dirs within this
 *  dir.  Extensions are stripped off.
 *    
 */
allsel()
    {
    static char     lastdir[MAXLINELEN];
    static int      totalnames;
    char            dta[80];
    char            *np = { DEFSRCH1 };
    int             counter;
    int             j;
    int             longlen;
    int             rowcnt;
    int             colcnt;
    int             namewidth;
    char            *s1;
    char            tmpbuf[MAXLINELEN];
    char            row[TERMWID+1];

    set_dta( dta );             /* for MSDOS */

    getcwd( currdir, MAXLINELEN );       /* save our startind directory */

    if ( second || ( strcmp( lastdir, currdir ) != 0 ) )
        {
        counter = 0;
        totalnames = 0;
        while( ( dir_get( np, ALL ) == 0 ) && ( counter < (MAXENT-1) ) )
            {
            np = NULL;
            strcpy ( tmpbuf, &dta[30] );
            if ( tmpbuf[0] != '.' )  /* if it is a directory or root file */
                {
                if ( ( s1 = strchr( tmpbuf, '.' ) ) == NULL )
                    {                               /* look for ext */
                    strcpy( dent[counter++], tmpbuf );
                    totalnames++;
                    }
                else if ( strcmp( s1, HELPEX ) == 0 )   /* its a .HLP file */
                    {                               /* look for ext */
                    *s1 = 0;                        /* terminate after fn */
                    strcpy( dent[counter++], tmpbuf );
                    totalnames++;
                    }
                }
            }
        dent[counter][0] = 0;           /* terminating entry */

        /* sort the names in ascending order with exchange algorithm */
        for(counter=0; counter < totalnames-1; counter++)
            for(j=counter+1; j <totalnames; j++)
                if (strcmp(dent[counter],dent[j]) > 0) 
                    {
                    strcpy( tmpbuf, dent[counter] );
                    strcpy( dent[counter], dent[j] );
                    strcpy( dent[j], tmpbuf );
                    }
        }
    if (totalnames == 0) 
        return(0);

/* this next section will be made into a subroutine */
/* rebuild code from here --\/ */
    longlen = 0;
    for(counter=0; counter < totalnames; counter++ )
        longlen = ((j=strlen(dent[counter]))>longlen)?j:longlen;
 
    /* here print the names out in nice columns */
    namewidth = longlen + COLUMNSPACE;
    rowcnt = TERMWID / namewidth;
    colcnt = (totalnames + (rowcnt-1)) / rowcnt ;
    if (colcnt <= 0) 
        colcnt = 1;
    
/*    if (col_flag && rowcnt > 1)   */
    if ( rowcnt > 1 ) 
        {
        fprintf( stdout, "\n Topics:\n\n");
        for(counter=0; counter < colcnt ; counter++ ) 
            {
            for(j=0; j < TERMWID; row[j++] = ' ');
            row[j] = '\0';
            for(j=0, s1 = row; (counter+j) < totalnames; j += colcnt) 
                {
                row[strlen(row)] = ' ';
                strcpy(s1,dent[counter+j]);
                s1 = s1 + namewidth;
                }
            fprintf( stdout, "    %s\n",row);
            }
        putchar('\n');
        }
    else 
        {
        for(counter=0; counter < totalnames; counter++)
            fprintf( stdout, "%s\n", dent[counter] );
        }
/*                to here --/\ */
 
    return( 0 );
    }
 


void set_dta( dta )
char    *dta;
    {
    union REGS rg;

    rg.h.ah = 0x1a;
    rg.x.dx = ( unsigned int )dta;

    int86( 0x21, &rg, &rg );
    }  

dir_get( fn, type )
char    *fn;
int     type;
    {
    union REGS rg;

    if ( fn )
        {
        rg.x.dx = ( unsigned int )fn;
        rg.x.cx = type;
        rg.h.ah = 0x4e;

        int86( 0x21, &rg, &rg );
        if ( rg.x.cflag & 0x01 )
            return( rg.x.ax );
        }
    else
        {
        rg.h.ah = 0x4f;
            
        int86( 0x21, &rg, &rg );
        if ( rg.x.cflag & 0x01 )
            return( rg.x.ax );
        }
    return( 0 );
    }
    

/****************************************************************************
 *  gettok( is, delim, tb, rb )     - Find token in input string        
 *  char    *is;        Pointer to input string
 *  char    *delim;     Pointer to set of delimiting characters
 *  tb      *tb         Pointer to buffer to place token
 *  rb      *rb         Pointer to buffer to place ( input string - token )
 *
 *  This function reads the input string looking for a token delimited by
 *  the beginning of the input buffer or a character in the delimiting set
 *  on the leading edge, and the end of the input buffer or a delimiting 
 *  character on the trailing edge.  The first character of the buffer 
 *  which is not a character in the set of delimiting characters is taken as
 *  the first character of the token.
 *
 *  If a pointer is passed in tb and/or rb, it is assumed to be a buffer of
 *  adequate size to hold the resultant string.  A NULL may be passed in these
 *  variables if the result is not desired.
 *
 *  Returns:    A pointer to the first not delimiting character.
 */
char *gettok( is, delim, tb, rb )
char    *is;     
char    *delim;  
char    *tb;
char    *rb;
    {
    char    *fc;                    /* pointer to first character of token */

    fc = NULL;                      /* default no token found */
        
    if ( tb )                       /* if user gave us a token buffer */
        *tb = 0;                    /* terminate the token buffer */        
    if ( rb )                       /* if user gave us a remainder buffer */
        *rb = 0;                    /* terminate the token buffer */        

    if ( is == NULL )               /* oops, got a 2001 */
        return( NULL );
        
    while ( *is && isdelim( *is, delim ) ) /* while leading delimiter */
        is++;                       /* skip it */
    if ( !*is )
        return( NULL );
    fc = is;                        /* save the pointer the first char */
    while ( *is && !isdelim( *is, delim ) )    /* while not a delimiter */
        {
        if ( tb )                   /* if user gave us a token buffer */
            {
            *tb = *is;              /* move the character */
            tb++;                   /* increment token buffer ptr */
            }
        is++;
        }
    if ( tb )                       /* if user gave us a token buffer */
        *tb = 0;                    /* terminate the token buffer */        

    if ( rb )                       /* if user gave us a remainder buffer */
        strcpy( rb, is );           /* copy the remainder */

    return( fc );
    }
            
isdelim( c, delim )
char    c;
char    *delim;
    {
    int     dc;                     /* delimiter count */
    int     i;                      /* temporary counter */
    char    *tc;                    /* termporary pointer */
    
    tc = delim;                     /* time to count the donuts */
    dc = 0;
    while  ( *tc++ )    
        dc++;

    for ( i=0; i<dc; i++ )          /* loop through the delimiters */
        {
        if ( c == delim[i] )        /* if a delimiter */
            return( 1 );            /* return found */
        }
    return( 0 );                    /* return not found */
    }            


more( fn )
char    *fn;
    {
    FILE    *sfd;
    char    buf[512];
    int     lct;
        
    lct = 0;
    scrnlen = DEFSCRLEN;
    
    if ( ( sfd = fopen( fn, "r" ) ) != NULL ) /* got a file */
        {
        while( fgets( buf, 512, sfd ) )
            {
            if ( lct == scrnlen )
                {
                lct = 0;
                if ( pgbrk() )
                    {
                    fclose( sfd );
                    return( 1 );
                    }
                }
            fputs( buf, stdout );
            lct++;
            }
        }
    fclose( sfd );
    return( 0 );
    }
    
pgbrk()
    {
    char    c;
    
    fprintf( stdout, "More (Y|N)" );
    c = ( char )kget();
    fputc( '\n', stdout );
    if ( c == 'N' )
        return( 1 );
    return( 0 );
    }

void stripnl( buf )
char    *buf;
    {
    while ( *buf )
        {
        if ( *buf == '\n' )
            *buf = 0;
        buf++;
        }
    }    
    

void setprompt()
    {
    char    *hs;                        /* pointer to the first / in helpdir */
    char    *cs;                        /* pointer to first / in current dir */
    char    *pp;                        /* pointer into prompt */
    
    getcwd( currdir, MAXLINELEN );       /* save our startind directory */

    hs = strchr( helpdir, '\\' );       /* ignore drive names */
    cs = strchr( currdir, '\\' );       /* ignore drive names */

    while ( *hs && ( *hs == *cs ) )     /* skip help directory root */
        {
        hs++;
        cs++;
        }
    
    pp = prompt;
    while ( *cs )                       /* use the rest for the prompt */
        {
        if ( *cs == '\\' )              /* convert to spaces for readablty */
            *cs = ' ';
        *pp++ = *cs++;
        }
    if ( pp != prompt )                 /* not at root... */
        *pp++ = ' ';                    /* add a pleasing space */

    *pp = 0;                            /* terminate the string */
    
    strcat( prompt, PROMPT );           /* and the default ending */
    }
      
void fstobs( str )                   
char    *str;
    {
    strupr( str );                  /* dos only understands one case */
    while ( *str )
        {
        if ( *str == '/' )          /* convert UNIX to MSDOS seps */
            *str = '\\';
        str++;
        }
    }
    
void handler()
    {
    char    c;
    
    if ( signal( SIGINT, handler ) < 0 )    /* uh oh */
        {
        fprintf( stderr, "%s could not set SIGINT.  Action undefined upon INT\n", progname );
        }

    fprintf( stdout, "Terminate process [y|N]" );
    c = ( char )kget();    
    fputc( '\n', stdout );
    if ( c == 'Y' )
        {
        chdir( olddir );                    /* go back to our origin */
        exit(0);
        }
    }
    
kget()
    {
    char c;
    
    while ( kbhit() == 0 )
        {
        }

    c = ( char )getche();
    c = toupper( c );

    return( c );
    }
======================= END of HELP.C ====================================
    
The following file is a MSC V4.0 makefile:
======================= Beginning of HELP ================================
OBJS = 	help.obj 
 
LIBFLAGS = 
# LIBFLAGS = /CO /MAP /LINE
 
# /AL means use large model
CFLAGS = /AS /Ot
# CFLAGS = /AS /Od /Zd /Zi 
 
# default rules
.c.obj:
	msc $(CFLAGS) $*;
 
.asm.obj:
	masm $*;
 
 
help.obj: help.c help.h
 
help.exe: $(OBJS)
        link$(LIBFLAGS) help,help;

#   help.exe: $(OBJS)
#		link$(LIBFLAGS) help,help ;
#	        mapsym /L help.map
======================= END of HELP ======================================