ftp.nice.ch/pub/next/unix/editor/joe2.3.N.bs.tar.gz#/joe2.3.N.bs/rc.c

This is rc.c in view mode; [Download] [Up]

/* rc file parser */

#include <stdio.h>
#include "zstr.h"
#include "macro.h"
#include "cmd.h"
#include "bw.h"
#include "help.h"
#include "vs.h"
#include "va.h"
#include "menu.h"
#include "umath.h"
#include "uedit.h"
#include "pw.h"
#include "path.h"
#include "w.h"
#include "tw.h"
#include "termcap.h"
#include "rc.h"

static struct context
 {
 struct context *next;
 char *name;
 KMAP *kmap;
 } *contexts=0;		/* List of named contexts */

/* Find a context of a given name- if not found, one with an empty kmap
 * is created.
 */

KMAP *getcontext(name)
char *name;
 {
 struct context *c;
 for(c=contexts;c;c=c->next) if(!zcmp(c->name,name)) return c->kmap;
 c=(struct context *)malloc(sizeof(struct context));
 c->next=contexts;
 c->name=zdup(name);
 contexts=c;
 return c->kmap=mkkmap();
 }

OPTIONS *options=0;
extern int mid, dspasis, dspctrl, force, help, pgamnt, square, csmode,
           nobackups, lightoff, exask, skiptop, noxon, lines, staen,
           columns, Baud, dopadding, orphan, marking, beep, keepup,
           nonotice;
extern char *backpath;

OPTIONS pdefault=
 { 0, 0, 0, 0, 76, 0, 0, 8, ' ', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

OPTIONS fdefault=
 { 0, 0, 0, 0, 76, 0, 0, 8, ' ', 1, "main", "\\i%n %m %M", " %S Ctrl-K H for help", 0, 0, 0, 0, 0, 0 };

void setopt(n,name)
OPTIONS *n;
char *name;
 {
 OPTIONS *o;
 for(o=options;o;o=o->next)
  if(rmatch(o->name,name))
   {
   *n= *o;
   return;
   }
 *n=fdefault;
 }

/* Set a global or local option 
 * returns 0 for no such option,
 *         1 for option accepted
 *         2 for option + argument accepted
 */

struct glopts
 {
 char *name;	/* Option name */
 int type;	/* 0 for global option flag
                   1 for global option numeric
                   2 for global option string
                   4 for local option flag
                   5 for local option numeric
                   6 for local option string
                   7 for local option numeric+1
                */
 int *set;	/* Address of global option */
 char *addr;	/* Local options structure member address */
 char *yes;	/* Message if option was turned on, or prompt string */
 char *no;	/* Message if option was turned off */
 char *menu;	/* Menu string */
 int ofst;	/* Local options structure member offset */
 int low;	/* Low limit for numeric options */
 int high;	/* High limit for numeric options */
 } glopts[]=
 {
  {"overwrite", 4, 0, (char *)&fdefault.overtype,
   "Overtype mode",
   "Insert mode",
   "T Overtype "},

  {"autoindent", 4, 0, (char *)&fdefault.autoindent,
   "Autoindent enabled",
   "Autindent disabled",
   "I Autoindent "},

  {"wordwrap", 4, 0, (char *)&fdefault.wordwrap,
   "Wordwrap enabled",
   "Wordwrap disabled",
   "Word wrap "},

  {"tab", 5, 0, (char *)&fdefault.tab,
   "Tab width (%d): ",
   0,
   "D Tab width ",0,1,64},

  {"lmargin", 7, 0, (char *)&fdefault.lmargin,
   "Left margin (%d): ",
   0,
   "Left margin ",0,1,64},

  {"rmargin", 7, 0, (char *)&fdefault.rmargin,
   "Right margin (%d): ",
   0,
   "Right margin ",0,8,256},

  {"square", 0, &square, 0,
   "Rectangle mode",
   "Text-stream mode",
   "X Rectangle mode " },

  {"indentc", 5, 0, (char *)&fdefault.indentc,
   "Indent char %d (SPACE=32, TAB=9, ^C to abort): ",
   0,
   " Indent char ",0,0,255},

  {"istep", 5, 0, (char *)&fdefault.istep,
   "Indent step %d (^C to abort): ",
   0,
   " Indent step ",0,1,64},

  {"french", 4, 0, (char *)&fdefault.french,
   "One space after periods for paragraph reformat",
   "Two spaces after periods for paragraph reformat",
   " french spacing "},

  {"spaces", 4, 0, (char *)&fdefault.spaces,
   "Inserting spaces when tab key is hit",
   "Inserting tabs when tab key is hit",
   " no tabs "},

  {"mid", 0, &mid, 0,
   "Cursor will be recentered on scrolls",
   "Cursor will not be recentered on scroll",
   "Center on scroll " },

  {"linums", 4, 0, (char *)&fdefault.linums,
   "Line numbers enabled",
   "Line numbers disabled",
   "N Line numbers "},

  {"marking", 0, &marking, 0,
   "Anchored block marking on",
   "Anchored block marking off",
   "Marking " },

  {"asis", 0, &dspasis, 0,
   "Characters above 127 shown as-is",
   "Characters above 127 shown in inverse",
   "Meta chars as-is "
    },

  {"force", 0, &force, 0,
   "Last line forced to have NL when file saved",
   "Last line not forces to have NL",
   "Force last NL " },

  {"nobackups", 0, &nobackups, 0,
   "Backup files will not be made",
   "Backup files will be made",
   " Disable backups " },

  {"lightoff", 0, &lightoff, 0,
   "Highlighting turned off after block operations",
   "Highlighting not turned off after block operations",
   "Auto unmark " },

  {"exask", 0, &exask, 0,
   "Prompt for filename in save & exit command",
   "Don't prompt for filename in save & exit command",
   "Exit ask "},

  {"beep", 0, &beep, 0,
   "Warning bell enabled", 
   "Warning bell disabled",
   "Beeps " },

  {"nosta", 0, &staen, 0,
   "Top-most status line disabled",
   "Top-most status line enabled",
   " Disable status line " },

  {"keepup", 0, &keepup, 0,
   "Status line updated constantly",
   "Status line updated once/sec",
   " Fast status line " },

  {"pg", 1, &pgamnt, 0,
   "Lines to keep for PgUp/PgDn or -1 for 1/2 window (%d): ",
   0,
   " No. PgUp/PgDn lines ", 0, -1, 64 },

  {"csmode", 0, &csmode, 0,
   "Start search after a search repeats previous search",
   "Start search always starts a new search",
   "Continued search " },

  {"rdonly", 4, 0, (char *)&fdefault.readonly,
   "Read only",
   "Full editing",
   "O Read only "},

  {"backpath", 2, (int *)&backpath, 0,
   "Backup files stored in (%s): ",
   0,
   "Path to backup files " },

  {"nonotice", 0, &nonotice, 0,
   0, 0, 0 },

  {"noxon", 0, &noxon, 0,
   0, 0, 0 },

  {"orphan", 0, &orphan, 0,
   0, 0, 0 },

  {"help", 0, &help, 0,
   0, 0, 0 },

  {"dopadding", 0, &dopadding, 0,
   0, 0, 0 },

  {"lines", 1, &lines, 0,
   0, 0, 0, 0, 2, 1024 },

  {"baud", 1, &Baud, 0,
   0, 0, 0, 0, 50, 32767 }, 

  {"columns", 1, &columns, 0,
   0, 0, 0, 0, 2, 1024 },

  {"skiptop", 1, &skiptop, 0,
   0, 0, 0, 0, 0, 64 }, 

  { 0, 0, 0 }
 };

int isiz=0;

void izopts()
 {
 int x;
 for(x=0;glopts[x].name;++x)
  switch(glopts[x].type)
   {
   case 4: case 5: case 6: case 7: case 8:
   glopts[x].ofst=glopts[x].addr-(char *)&fdefault;
   }
 isiz=1;
 }

int glopt(s,arg,options,set)
char *s, *arg;
OPTIONS *options;
 {
 int val;
 int ret=0;
 int st=1;
 int x;
 if(!isiz) izopts();
 if(s[0]=='-') st=0, ++s;
 for(x=0;glopts[x].name;++x)
  if(!zcmp(glopts[x].name,s))
   {
   switch(glopts[x].type)
    {
    case 0: if(set) *glopts[x].set=st;
    break;
    
    case 1: if(set && arg)
             {
             sscanf(arg,"%d",&val);
             if(val>=glopts[x].low && val<=glopts[x].high) *glopts[x].set=val;
             }
    break;

    case 2: if(set)
             if(arg) *(char **)glopts[x].set=zdup(arg);
             else *(char **)glopts[x].set=0;
    break;

    case 4: if(options) *(int *)((char *)options+glopts[x].ofst)=st;
            else if(set==2) *(int *)((char *)&fdefault+glopts[x].ofst)=st;
    break;
    
    case 5: if(arg)
             if(options)
              {
              sscanf(arg,"%d",&val);
              if(val>=glopts[x].low && val<=glopts[x].high)
               *(int *)((char *)options+glopts[x].ofst)=val;
              }
             else if(set==2)
              {
              sscanf(arg,"%d",&val);
              if(val>=glopts[x].low && val<=glopts[x].high)
               *(int *)((char *)&fdefault+glopts[x].ofst)=val;
              }
    break;

    case 7: if(arg)
             {
             int zz=0;
             sscanf(arg,"%d",&zz);
             if(zz>=glopts[x].low && zz <=glopts[x].high)
              {
              --zz;
              if(options) *(int *)((char *)options+glopts[x].ofst)=zz;
              else if(set==2) *(int *)((char *)&fdefault+glopts[x].ofst)=zz;
              }
             }
    break;
    }
   if((glopts[x].type&3)==0 || !arg) return 1;
   else return 2;
   }
 if(!zcmp(s,"lmsg"))
  {
  if(arg)
   {
   if(options) options->lmsg=zdup(arg);
   else if(set==2) fdefault.lmsg=zdup(arg);
   ret=2;
   }
  else ret=1;
  }
 else if(!zcmp(s,"rmsg"))
  {
  if(arg)
   {
   if(options) options->rmsg=zdup(arg);
   else if(set==2) fdefault.rmsg=zdup(arg);
   ret=2;
   }
  else ret=1;
  }
 else if(!zcmp(s,"keymap"))
  {
  if(arg)
   {
   int y;
   for(y=0;!cwhitel(arg[y]);++y);
   if(!arg[y]) arg[y]=0;
   if(options && y) options->context=zdup(arg);
   ret=2;
   }
  else ret=1;
  }
 else if(!zcmp(s,"mnew"))
  {
  if(arg)
   {
   int sta;
   if(options) options->mnew=mparse(NULL,arg,&sta);
   else if(set==2) fdefault.mnew=mparse(NULL,arg,&sta);
   ret=2;
   }
  else ret=1;
  }
 else if(!zcmp(s,"mold"))
  {
  if(arg)
   {
   int sta;
   if(options) options->mold=mparse(NULL,arg,&sta);
   else if(set==2) fdefault.mold=mparse(NULL,arg,&sta);
   ret=2;
   }
  else ret=1;
  }
 else if(!zcmp(s,"msnew"))
  {
  if(arg)
   {
   int sta;
   if(options) options->msnew=mparse(NULL,arg,&sta);
   else if(set==2) fdefault.msnew=mparse(NULL,arg,&sta);
   ret=2;
   }
  else ret=1;
  }
 else if(!zcmp(s,"msold"))
  {
  if(arg)
   {
   int sta;
   if(options) options->msold=mparse(NULL,arg,&sta);
   else if(set==2) fdefault.msold=mparse(NULL,arg,&sta);
   ret=2;
   }
  else ret=1;
  }
 done: return ret;
 }

static int optx=0;

int doabrt1(bw,xx)
BW *bw;
int *xx;
 {
 free(xx);
 return -1;
 }

int doopt1(bw,s,xx,notify)
BW *bw;
char *s;
int *xx;
int *notify;
 {
 int ret=0;
 int x= *xx;
 int v;
 free(xx);
 switch(glopts[x].type)
  {
  case 1:
   v=calc(bw,s);
   if(merr) msgnw(bw,merr), ret= -1;
   else if(v>=glopts[x].low && v<=glopts[x].high) *glopts[x].set=v;
   else msgnw(bw,"Value out of range"), ret= -1;
   break;
  case 2: if(s[0]) *(char **)glopts[x].set=zdup(s); break;
  case 5:
   v=calc(bw,s);
   if(merr) msgnw(bw,merr), ret= -1;
   else if(v>=glopts[x].low && v<=glopts[x].high) *(int *)((char *)&bw->o+glopts[x].ofst)=v;
   else msgnw(bw,"Value out of range"), ret= -1;
   break;
  case 7:
   v=calc(bw,s)-1.0;
   if(merr) msgnw(bw,merr), ret= -1;
   else if(v>=glopts[x].low && v<=glopts[x].high) *(int *)((char *)&bw->o+glopts[x].ofst)=v;
   else msgnw(bw,"Value out of range"), ret= -1;
   break;
  }
 vsrm(s);
 bw->b->o=bw->o;
 wfit(bw->parent->t);
 updall();
 if(notify) *notify=1;
 return ret;
 }

int doopt(m,x,object,flg)
MENU *m;
void *object;
 {
 BW *bw=m->parent->win->object;
 int *xx;
 char buf[80];
 int *notify=m->parent->notify;
 switch(glopts[x].type)
  {
  case 0:
  if(!flg) *glopts[x].set= !*glopts[x].set;
  else if(flg==1) *glopts[x].set= 1;
  else *glopts[x].set=0;
  uabort(m,MAXINT);
  msgnw(bw,*glopts[x].set?glopts[x].yes:glopts[x].no);
  break;

  case 4:
  if(!flg) *(int *)((char *)&bw->o+glopts[x].ofst)= !*(int *)((char *)&bw->o+glopts[x].ofst);
  else if(flg==1) *(int *)((char *)&bw->o+glopts[x].ofst)=1;
  else *(int *)((char *)&bw->o+glopts[x].ofst)=0;
  uabort(m,MAXINT);
  msgnw(bw,*(int *)((char *)&bw->o+glopts[x].ofst)?glopts[x].yes:glopts[x].no);
  if(glopts[x].ofst==(char *)&fdefault.readonly-(char *)&fdefault)
   bw->b->rdonly=bw->o.readonly;
  break;

  case 1:
  sprintf(buf,glopts[x].yes,*glopts[x].set);
  xx=(int *)malloc(sizeof(int)); *xx=x;
  m->parent->notify=0;
  uabort(m,MAXINT);
  if(wmkpw(bw,buf,NULL,doopt1,NULL,doabrt1,utypebw,xx,notify)) return 0;
  else return -1;

  case 2:
  if(*(char **)glopts[x].set) sprintf(buf,glopts[x].yes,*(char **)glopts[x].set);
  else sprintf(buf,glopts[x].yes,"");
  xx=(int *)malloc(sizeof(int)); *xx=x;
  m->parent->notify=0;
  uabort(m,MAXINT);
  if(wmkpw(bw,buf,NULL,doopt1,NULL,doabrt1,utypebw,xx,notify)) return 0;
  else return -1;

  case 5:
  sprintf(buf,glopts[x].yes,*(int *)((char *)&bw->o+glopts[x].ofst));
  goto in;
  
  case 7:
  sprintf(buf,glopts[x].yes,*(int *)((char *)&bw->o+glopts[x].ofst)+1);
  in: xx=(int *)malloc(sizeof(int)); *xx=x;
  m->parent->notify=0;
  uabort(m,MAXINT);
  if(wmkpw(bw,buf,NULL,doopt1,NULL,doabrt1,utypebw,xx,notify)) return 0;
  else return -1;
  }
 if(notify) *notify=1;
 bw->b->o=bw->o;
 wfit(bw->parent->t);
 updall();
 return 0;
 }

int doabrt(m,x,s)
MENU *m;
char **s;
 {
 optx=x;
 for(x=0;s[x];++x) free(s[x]);
 free(s);
 return -1;
 }

int umode(bw)
BW *bw;
 {
 int size;
 char **s;
 int x;
 bw->b->o.readonly=bw->o.readonly=bw->b->rdonly;
 for(size=0;glopts[size].menu;++size);
 s=(char **)malloc(sizeof(char *)*(size+1));
 for(x=0;x!=size;++x)
  {
  s[x]=(char *)malloc(40);
  switch(glopts[x].type)
   {
   case 0: sprintf(s[x],"%s%s",glopts[x].menu,*glopts[x].set?"ON":"OFF");
   break;
   
   case 1: sprintf(s[x],"%s%d",glopts[x].menu,*glopts[x].set);
   break;

   case 2: zcpy(s[x],glopts[x].menu);
   break;

   case 4: sprintf(s[x],"%s%s",glopts[x].menu,*(int *)((char *)&bw->o+glopts[x].ofst)?"ON":"OFF");
   break;
   
   case 5: sprintf(s[x],"%s%d",glopts[x].menu,*(int *)((char *)&bw->o+glopts[x].ofst));
   break;

   case 7: sprintf(s[x],"%s%d",glopts[x].menu,*(int *)((char *)&bw->o+glopts[x].ofst)+1);
   break;
   }
  }
 s[x]=0;
 if(mkmenu(bw,s,doopt,doabrt,NULL,optx,s,NULL)) return 0;
 else return -1;
 }


/* Process rc file
 * Returns 0 if the rc file was succefully processed
 *        -1 if the rc file couldn't be opened
 *         1 if there was a syntax error in the file
 */

int nhelp=0;			/* No. help screens so far */

int procrc(cap,name)
CAP *cap;
char *name;
 {
 OPTIONS *o=0;			/* Current options */
 KMAP *context=0;		/* Current context */
 unsigned char buf[1024];	/* Input buffer */
 FILE *fd;			/* rc file */
 int line=0;			/* Line number */
 int err=0;			/* Set to 1 if there was a syntax error */
 ossep(zcpy(buf,name));
#ifdef __MSDOS__
 fd=fopen(buf,"rt");
#else
 fd=fopen(buf,"r");
#endif
 
 if(!fd) return -1;		/* Return if we couldn't open the rc file */
 
 fprintf(stderr,"Processing '%s'...",name); fflush(stderr);
 
 while(++line, fgets(buf,1024,fd))
  switch(buf[0])
   {
   case ' ': case '\t': case '\n': case '\f': case 0:
   break;	/* Skip comment lines */

   case '*':	/* Select file types for file-type dependant options */
    {
    int x;
    o=(OPTIONS *)malloc(sizeof(OPTIONS));
    *o=fdefault;
    for(x=0;buf[x] && buf[x]!='\n' && buf[x]!=' ' && buf[x]!='\t';++x);
    buf[x]=0;
    o->next=options;
    options=o;
    o->name=zdup(buf);
    }
   break;
   
   case '-':	/* Set an option */
    {
    unsigned char *opt=buf+1;
    int x;
    unsigned char *arg=0;
    for(x=0;buf[x] && buf[x]!='\n' && buf[x]!=' ' && buf[x]!='\t';++x);
    if(buf[x] && buf[x]!='\n')
     {
     buf[x]=0;
     for(arg=buf+ ++x;buf[x] && buf[x]!='\n';++x);
     }
    buf[x]=0;
    if(!glopt(opt,arg,o,2))
     {
     err=1;
     fprintf(stderr,"\n%s %d: Unknown option %s",name,line,opt);
     }
    }
   break;
   
   case '{':	/* Enter help text */
    {
    int bfl;
    struct help *tmp=(struct help *)malloc(sizeof(struct help));
    nhelp++;
    tmp->next=first_help;
    first_help=tmp;
    tmp->name=vsncpy(NULL,0,sz(buf+1)-1);
    help_names=vaadd(help_names,tmp->name);
    tmp->hlptxt=0;
    tmp->hlpsiz=0;
    tmp->hlpbsz=0;
    tmp->hlplns=0;
    up:
    if(++line, !fgets(buf,256,fd))
     {
     err=1;
     fprintf(stderr,"\n%s %d: End of joerc file occured before end of help text",name,line);
     break;
     }
    if(buf[0]=='}')
     {
     if(!hlptxt)
      hlptxt=tmp->hlptxt,
      hlpsiz=tmp->hlpsiz,
      hlpbsz=tmp->hlpbsz,
      hlplns=tmp->hlplns;
     continue;
     }
    bfl=zlen(buf);
    if(tmp->hlpsiz+bfl>tmp->hlpbsz)
     {
     if(tmp->hlptxt) tmp->hlptxt=(char *)realloc(tmp->hlptxt,tmp->hlpbsz+bfl+1024);
     else tmp->hlptxt=(char *)malloc(bfl+1024), tmp->hlptxt[0]=0;
     tmp->hlpbsz+=bfl+1024;
     }
    zcpy(tmp->hlptxt+tmp->hlpsiz,buf);
    tmp->hlpsiz+=bfl;
    ++tmp->hlplns;
    goto up;
    }
   break;

   case ':':	/* Select context */
    {
    int x, c;
    for(x=1;!cwhitef(buf[x]);++x);
    c=buf[x]; buf[x]=0;
    if(x!=1)
     if(!zcmp(buf+1,"def"))
      {
      int y;
      for(buf[x]=c;cwhite(buf[x]);++x);
      for(y=x;!cwhitef(buf[y]);++y);
      c=buf[y]; buf[y]=0;
      if(y!=x)
       {
       int sta;
       MACRO *m;
       if(cwhite(c) && (m=mparse(NULL,buf+y+1,&sta)))
        addcmd(buf+x,m);
       else
        {
        err=1;
        fprintf(stderr,"\n%s %d: macro missing from :def",name,line);
        }
       }
      else
       {
       err=1;
       fprintf(stderr,"\n%s %d: command name missing from :def",name,line);
       }
      }
     else if(!zcmp(buf+1,"inherit"))
      if(context)
       {
       for(buf[x]=c;cwhite(buf[x]);++x);
       for(c=x;!cwhitef(buf[c]);++c);
       buf[c]=0;
       if(c!=x) kcpy(context,getcontext(buf+x));
       else
        {
        err=1;
        fprintf(stderr,"\n%s %d: context name missing from :inherit",name,line);
        }
       }
      else
       {
       err=1;
       fprintf(stderr,"\n%s %d: No context selected for :inherit",name,line);
       }
     else if(!zcmp(buf+1,"include"))
      {
      for(buf[x]=c;cwhite(buf[x]);++x);
      for(c=x;!cwhitef(buf[c]);++c);
      buf[c]=0;
      if(c!=x)
       {
       switch(procrc(buf+x))
        {
        case 1: err=1; break;
        case -1: fprintf(stderr,"\n%s %d: Couldn't open %s",name,line,buf+x);
                 err=1; break;
        }
       context=0;
       o=0;
       }
      else
       {
       err=1;
       fprintf(stderr,"\n%s %d: :include missing file name",name,line);
       }
      }
     else if(!zcmp(buf+1,"delete"))
      if(context)
       {
       int y;
       for(buf[x]=c;cwhite(buf[x]);++x);
       for(y=x;buf[y]!=0 && buf[y]!='\t' && buf[y]!='\n' &&
               (buf[y]!=' ' || buf[y+1]!=' ');++y);
       buf[y]=0;
       kdel(context,buf+x);
       }
      else
       {
       err=1;
       fprintf(stderr,"\n%s %d: No context selected for :delete",name,line);
       }
     else context=getcontext(buf+1);
    else
     {
     err=1;
     fprintf(stderr,"\n%s %d: Invalid context name",name,line);
     }
    }
   break;

   default:	/* Get key-sequence to macro binding */
    {
    int x, y, c;
    MACRO *m;
    if(!context)
     {
     err=1;
     fprintf(stderr,"\n%s %d: No context selected for macro to key-sequence binding",name,line);
     break;
     }

    m=0;
    macroloop:
    m=mparse(m,buf,&x);
    if(x== -1)
     {
     err=1;
     fprintf(stderr,"\n%s %d: Unknown command in macro",name,line);
     break;
     }
    else if(x== -2)
     {
     fgets(buf,1024,fd);
     goto macroloop;
     }
    if(!m) break;

    /* Skip to end of key sequence */
    for(y=x;buf[y]!=0 && buf[y]!='\t' && buf[y]!='\n' && 
            (buf[y]!=' ' || buf[y+1]!=' ');++y);
    buf[y]=0;

    /* Add binding to context */
    if(kadd(cap,context,buf+x,m)== -1)
     {
     fprintf(stderr,"\n%s %d: Bad key sequence '%s'",name,line,buf+x);
     err=1;
     }
    }
   break;
   }
 fclose(fd);			/* Close rc file */

 /* Print proper ending string */
 if(err) fprintf(stderr,"\ndone\n");
 else fprintf(stderr,"done\n");

 return err;			/* 0 for success, 1 for syntax error */
 }

void izhelp()
 {
 struct help *tmp;
 /* Convert list of help screens into an array */
 if(nhelp)
  {
  help_structs=(struct help **) malloc(sizeof(struct help *)*(nhelp+1));
  help_structs[nhelp]=0;
  tmp=first_help;
  while(nhelp--)
   {
   help_structs[nhelp]=tmp;
   tmp=tmp->next;
   }
  }
 }

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.