Home | History | Annotate | Line # | Download | only in qsubst
qsubst.c revision 1.1
      1 /*
      2  * qsubst -- designed for renaming routines existing in a whole bunch
      3  *  of files.  Needs -ltermcap.
      4  *
      5  * Usage:
      6  *
      7  * qsubst str1 str2 [ options ]
      8  *
      9  * qsubst reads its options (see below) to get a list of files.  For
     10  *  each file on this list, it then replaces str1 with str2 wherever
     11  *  possible in that file, depending on user input (see below).  The
     12  *  result is written back onto the original file.
     13  *
     14  * For each possible substitution, the user is prompted with a few
     15  *  lines before and after the line containing the string to be
     16  *  substituted.  The string itself is displayed using the terminal's
     17  *  standout mode, if any.  Then one character is read from the
     18  *  terminal.  This is then interpreted as follows (this is designed to
     19  *  be like Emacs' query-replace-string):
     20  *
     21  *	space	replace this occurrence and go on to the next one
     22  *	.	replace this occurrence and don't change any more in
     23  *		this file (ie, go on to the next file).
     24  *	,	tentatively replace this occurrence.  The lines as they
     25  *		would look if the substitution were made are printed
     26  *		out.  Then another character is read and it is used to
     27  *		decide the result (possibly undoing the tentative
     28  *		replacement).
     29  *	n	don't change this one, but go on to the next one
     30  *	^G	don't change this one or any others in this file, but
     31  *		instead go on to the next file.
     32  *	!	change the rest in this file without asking, then go on
     33  *		to the next file (at which point qsubst will start
     34  *		asking again).
     35  *	?	print out the current filename and ask again.
     36  *
     37  * The first two arguments to qsubst are always the string to replace
     38  *  and the string to replace it with.  The options are as follows:
     39  *
     40  *	-w	The search string is considered as a C symbol; it must
     41  *		be bounded by non-symbol characters.  This option
     42  *		toggles.  (`w' for `word'.)
     43  *	-!	Enter ! mode automatically at the beginning of each
     44  *		file.
     45  *	-go	Same as -!
     46  *	-noask	Same as -!
     47  *	-nogo	Negate -go
     48  *	-ask	Negate -noask (same as -nogo)
     49  *	-cN	(N is a number) Give N lines of context above and below
     50  *		the line with the match when prompting the user.
     51  *	-CAN	(N is a number) Give N lines of context above the line
     52  *		with the match when prompting the user.
     53  *	-CBN	(N is a number) Give N lines of context below the line
     54  *		with the match when prompting the user.
     55  *	-f filename
     56  *		The filename following the -f argument is one of the
     57  *		files qsubst should perform substitutions in.
     58  *	-F filename
     59  *		qsubst should read the named file to get the names of
     60  *		files to perform substitutions in.  The names should
     61  *		appear one to a line.
     62  *
     63  * The default amount of context is -c2, that is, two lines above and
     64  *  two lines below the line with the match.
     65  *
     66  * Arguments not beginning with a - sign in the options field are
     67  *  implicitly preceded by -f.  Thus, -f is really needed only when the
     68  *  file name begins with a - sign.
     69  *
     70  * qsubst reads its options in order and processes files as it gets
     71  *  them.  This means, for example, that a -go will affect only files
     72  *  from -f or -F options appearing after the -go option.
     73  *
     74  * The most context you can get is ten lines each, above and below
     75  *  (corresponding to -c10).
     76  *
     77  * Str1 is limited to 512 characters; there is no limit on the size of
     78  *  str2.  Neither one may contain a NUL.
     79  *
     80  * NULs in the file may cause qsubst to make various mistakes.
     81  *
     82  * If any other program modifies the file while qsubst is running, all
     83  *  bets are off.
     84  *
     85  * This program is in the public domain.  Anyone may use it in any way
     86  *  for any purpose.  Of course, it's also up to you to determine
     87  *  whether what it does is suitable for you; the above comments may
     88  *  help, but I can't promise they're accurate.  It's free, and you get
     89  *  what you pay for.
     90  *
     91  * If you find any bugs I would appreciate hearing about them,
     92  *  especially if you also fix them.
     93  *
     94  *					der Mouse
     95  *
     96  *			       mouse (at) rodents.montreal.qc.ca
     97  */
     98 
     99 #include <stdio.h>
    100 #include <ctype.h>
    101 #include <errno.h>
    102 #include <unistd.h>
    103 #include <stdlib.h>
    104 #include <signal.h>
    105 #include <strings.h>
    106 #include <termios.h>
    107 #include <sys/file.h>
    108 #include <unused-arg.h>
    109 
    110 /* These belong in an include file, but which one? */
    111 extern int tgetent(char *, const char *);
    112 extern int tgetflag(const char *);
    113 extern const char *tgetstr(const char *, char **);
    114 extern void tputs(const char *, int, int (*)(char));
    115 
    116 extern const char *__progname;
    117 
    118 #define MAX_C_A 10
    119 #define MAX_C_B 10
    120 #define BUF_SIZ 1024
    121 
    122 static int debugging;
    123 static FILE *tempf;
    124 static long int tbeg;
    125 static FILE *workf;
    126 static char *str1;
    127 static char *str2;
    128 static int s1l;
    129 static int s2l;
    130 static long int nls[MAX_C_A+1];
    131 static char buf[(BUF_SIZ*2)+2];
    132 static char *bufp;
    133 static char *bufp0;
    134 static char *bufpmax;
    135 static int rahead;
    136 static int cabove;
    137 static int cbelow;
    138 static int wordmode;
    139 static int flying;
    140 static int flystate;
    141 static int allfly;
    142 static const char *nullstr = "";
    143 static int ul_;
    144 static char *current_file;
    145 static const char *beginul;
    146 static const char *endul;
    147 static char tcp_buf[1024];
    148 static char cap_buf[1024];
    149 static struct termios orig_tio;
    150 
    151 static void tstp_self(void)
    152 {
    153  void (*old_tstp)(int);
    154  int mask;
    155 
    156  mask = sigblock(0);
    157  kill(getpid(),SIGTSTP);
    158  old_tstp = signal(SIGTSTP,SIG_DFL);
    159  sigsetmask(mask&~sigmask(SIGTSTP));
    160  signal(SIGTSTP,old_tstp);
    161 }
    162 
    163 static void sigtstp(UNUSED_ARG(int sig))
    164 {
    165  struct termios tio;
    166 
    167  if (tcgetattr(0,&tio) < 0)
    168   { tstp_self();
    169     return;
    170   }
    171  tcsetattr(0,TCSAFLUSH|TCSASOFT,&orig_tio);
    172  tstp_self();
    173  tcsetattr(0,TCSADRAIN|TCSASOFT,&tio);
    174 }
    175 
    176 static void limit_above_below(void)
    177 {
    178  if (cabove > MAX_C_A)
    179   { cabove = MAX_C_A;
    180   }
    181  if (cbelow > MAX_C_B)
    182   { cbelow = MAX_C_B;
    183   }
    184 }
    185 
    186 static int issymchar(char c)
    187 {
    188  return( isascii(c) &&
    189 	 ( isalnum(c) ||
    190 	   (c == '_') ||
    191 	   (c == '$') ) );
    192 }
    193 
    194 static int foundit(void)
    195 {
    196  if (wordmode)
    197   { return( !issymchar(bufp[-1]) &&
    198 	    !issymchar(bufp[-2-s1l]) &&
    199 	    !bcmp(bufp-1-s1l,str1,s1l) );
    200   }
    201  else
    202   { return(!bcmp(bufp-s1l,str1,s1l));
    203   }
    204 }
    205 
    206 static int putcharf(char c)
    207 {
    208  putchar(c);
    209  return(0); /* ??? */
    210 }
    211 
    212 static void put_ul(char *s)
    213 {
    214  if (ul_)
    215   { for (;*s;s++)
    216      { printf("_\b%c",*s);
    217      }
    218   }
    219  else
    220   { tputs(beginul,1,putcharf);
    221     fputs(s,stdout);
    222     tputs(endul,1,putcharf);
    223   }
    224 }
    225 
    226 static int getc_cbreak(void)
    227 {
    228  struct termios tio;
    229  struct termios otio;
    230  char c;
    231 
    232  if (tcgetattr(0,&tio) < 0) return(getchar());
    233  otio = tio;
    234  tio.c_lflag &= ~(ICANON|ECHOKE|ECHOE|ECHO|ECHONL);
    235  tio.c_cc[VMIN] = 1;
    236  tio.c_cc[VTIME] = 0;
    237  tcsetattr(0,TCSANOW|TCSASOFT,&tio);
    238  switch (read(0,&c,1))
    239   { case -1:
    240        break;
    241     case 0:
    242        break;
    243     case 1:
    244        break;
    245   }
    246  tcsetattr(0,TCSANOW|TCSASOFT,&tio);
    247  return(c);
    248 }
    249 
    250 static int doit(void)
    251 {
    252  long int save;
    253  int i;
    254  int lastnl;
    255  int use_replacement;
    256 
    257  if (flying)
    258   { return(flystate);
    259   }
    260  use_replacement = 0;
    261  save = ftell(workf);
    262  do
    263   { for (i=MAX_C_A-cabove;nls[i]<0;i++) ;
    264     fseek(workf,nls[i],0);
    265     for (i=save-nls[i]-rahead;i;i--)
    266      { putchar(getc(workf));
    267      }
    268     put_ul(use_replacement?str2:str1);
    269     fseek(workf,save+s1l-rahead,0);
    270     lastnl = 0;
    271     i = cbelow + 1;
    272     while (i > 0)
    273      { int c;
    274        c = getc(workf);
    275        if (c == EOF)
    276 	{ clearerr(workf);
    277 	  break;
    278 	}
    279        putchar(c);
    280        lastnl = 0;
    281        if (c == '\n')
    282 	{ i --;
    283 	  lastnl = 1;
    284 	}
    285      }
    286     if (! lastnl) printf("\n[no final newline] ");
    287     fseek(workf,save,0);
    288     i = -1;
    289     while (i == -1)
    290      { switch (getc_cbreak())
    291 	{ case ' ':
    292 	     i = 1;
    293 	     break;
    294 	  case '.':
    295 	     i = 1;
    296 	     flying = 1;
    297 	     flystate = 0;
    298 	     break;
    299 	  case 'n':
    300 	     i = 0;
    301 	     break;
    302 	  case '\7':
    303 	     i = 0;
    304 	     flying = 1;
    305 	     flystate = 0;
    306 	     break;
    307 	  case '!':
    308 	     i = 1;
    309 	     flying = 1;
    310 	     flystate = 1;
    311 	     break;
    312 	  case ',':
    313 	     use_replacement = ! use_replacement;
    314 	     i = -2;
    315 	     printf("(using %s string gives)\n",use_replacement?"new":"old");
    316 	     break;
    317 	  case '?':
    318 	     printf("File is `%s'\n",current_file);
    319 	     break;
    320 	  default:
    321 	     putchar('\7');
    322 	     break;
    323 	}
    324      }
    325   } while (i < 0);
    326  if (i)
    327   { printf("(replacing");
    328   }
    329  else
    330   { printf("(leaving");
    331   }
    332  if (flying)
    333   { if (flystate == i)
    334      { printf(" this and all the rest");
    335      }
    336     else if (flystate)
    337      { printf(" this, replacing all the rest");
    338      }
    339     else
    340      { printf(" this, leaving all the rest");
    341      }
    342   }
    343  printf(")\n");
    344  return(i);
    345 }
    346 
    347 static void add_shift(long int *a, long int e, int n)
    348 {
    349  int i;
    350 
    351  n --;
    352  for (i=0;i<n;i++)
    353   { a[i] = a[i+1];
    354   }
    355  a[n] = e;
    356 }
    357 
    358 static void process_file(char *fn)
    359 {
    360  int i;
    361  long int n;
    362  int c;
    363 
    364  workf = fopen(fn,"r+");
    365  if (workf == NULL)
    366   { fprintf(stderr,"%s: cannot read %s\n",__progname,fn);
    367     return;
    368   }
    369  printf("(file: %s)\n",fn);
    370  current_file = fn;
    371  for (i=0;i<=MAX_C_A;i++)
    372   { nls[i] = -1;
    373   }
    374  nls[MAX_C_A] = 0;
    375  tbeg = -1;
    376  if (wordmode)
    377   { bufp0 = &buf[1];
    378     rahead = s1l + 1;
    379     buf[0] = '\0';
    380   }
    381  else
    382   { bufp0 = &buf[0];
    383     rahead = s1l;
    384   }
    385  if (debugging)
    386   { printf("[rahead = %d, bufp0-buf = %d]\n",rahead,bufp0-&buf[0]);
    387   }
    388  n = 0;
    389  bufp = bufp0;
    390  bufpmax = &buf[sizeof(buf)-s1l-2];
    391  flying = allfly;
    392  flystate = 1;
    393  while (1)
    394   { c = getc(workf);
    395     if (c == EOF)
    396      { if (tbeg >= 0)
    397 	{ if (bufp > bufp0) fwrite(bufp0,1,bufp-bufp0,tempf);
    398 	  fseek(workf,tbeg,0);
    399 	  n = ftell(tempf);
    400 	  fseek(tempf,0L,0);
    401 	  for (;n;n--)
    402 	   { putc(getc(tempf),workf);
    403 	   }
    404 	  fflush(workf);
    405 	  ftruncate(fileno(workf),ftell(workf));
    406 	}
    407        fclose(workf);
    408        return;
    409      }
    410     *bufp++ = c;
    411     n ++;
    412     if (debugging)
    413      { printf("[got %c, n now %ld, bufp-buf %d]\n",c,n,bufp-bufp0);
    414      }
    415     if ((n >= rahead) && foundit() && doit())
    416      { int wbehind;
    417        if (debugging)
    418 	{ printf("[doing change]\n");
    419 	}
    420        wbehind = 1;
    421        if (tbeg < 0)
    422 	{ tbeg = ftell(workf) - rahead;
    423 	  fseek(tempf,0L,0);
    424 	  if (debugging)
    425 	   { printf("[tbeg set to %d]\n",(int)tbeg);
    426 	   }
    427 	  wbehind = 0;
    428 	}
    429        if (bufp[-1] == '\n') add_shift(nls,ftell(workf),MAX_C_A+1);
    430        if ((n > rahead) && wbehind)
    431 	{ fwrite(bufp0,1,n-rahead,tempf);
    432 	  if (debugging)
    433 	   { printf("[writing %ld from bufp0]\n",n-rahead);
    434 	   }
    435 	}
    436        fwrite(str2,1,s2l,tempf);
    437        n = rahead - s1l;
    438        if (debugging)
    439 	{ printf("[n now %ld]\n",n);
    440 	}
    441        if (n > 0)
    442 	{ bcopy(bufp-n,bufp0,n);
    443 	  if (debugging)
    444 	   { printf("[copying %ld back]\n",n);
    445 	   }
    446 	}
    447        bufp = bufp0 + n;
    448      }
    449     else
    450      { if (bufp[-1] == '\n') add_shift(nls,ftell(workf),MAX_C_A+1);
    451        if (bufp >= bufpmax)
    452 	{ if (tbeg >= 0)
    453 	   { fwrite(bufp0,1,n-rahead,tempf);
    454 	     if (debugging)
    455 	      { printf("[flushing %ld]\n",n-rahead);
    456 	      }
    457 	   }
    458 	  n = rahead;
    459 	  bcopy(bufp-n,bufp0,n);
    460 	  if (debugging)
    461 	   { printf("[n now %ld]\n[copying %ld back]\n",n,n);
    462 	   }
    463 	  bufp = bufp0 + n;
    464 	}
    465      }
    466   }
    467 }
    468 
    469 static void process_indir_file(char *fn)
    470 {
    471  char newfn[1024];
    472  FILE *f;
    473 
    474  f = fopen(fn,"r");
    475  if (f == NULL)
    476   { fprintf(stderr,"%s: cannot read %s\n",__progname,fn);
    477     return;
    478   }
    479  while (fgets(newfn,sizeof(newfn),f) == newfn)
    480   { newfn[strlen(newfn)-1] = '\0';
    481     process_file(newfn);
    482   }
    483  fclose(f);
    484 }
    485 
    486 int main(int, char **);
    487 int main(int ac, char **av)
    488 {
    489  int skip;
    490  char *cp;
    491 
    492  if (ac < 3)
    493   { fprintf(stderr,"Usage: %s str1 str2 [ -w -! -noask -go -f file -F file ]\n",
    494 				__progname);
    495     exit(1);
    496   }
    497  cp = getenv("TERM");
    498  if (cp == 0)
    499   { beginul = nullstr;
    500     endul = nullstr;
    501   }
    502  else
    503   { if (tgetent(tcp_buf,cp) != 1)
    504      { beginul = nullstr;
    505        endul = nullstr;
    506      }
    507     else
    508      { cp = cap_buf;
    509        if (tgetflag("os") || tgetflag("ul"))
    510 	{ ul_ = 1;
    511 	}
    512        else
    513 	{ ul_ = 0;
    514 	  beginul = tgetstr("us",&cp);
    515 	  if (beginul == 0)
    516 	   { beginul = tgetstr("so",&cp);
    517 	     if (beginul == 0)
    518 	      { beginul = nullstr;
    519 		endul = nullstr;
    520 	      }
    521 	     else
    522 	      { endul = tgetstr("se",&cp);
    523 	      }
    524 	   }
    525 	  else
    526 	   { endul = tgetstr("ue",&cp);
    527 	   }
    528 	}
    529      }
    530   }
    531   { static char tmp[] = "/tmp/qsubst.XXXXXX";
    532     int fd;
    533     fd = mkstemp(&tmp[0]);
    534     if (fd < 0)
    535      { fprintf(stderr,"%s: cannot create temp file: %s\n",__progname,strerror(errno));
    536        exit(1);
    537      }
    538     tempf = fdopen(fd,"w+");
    539   }
    540  if ( (access(av[1],R_OK|W_OK) == 0) &&
    541       (access(av[ac-1],R_OK|W_OK) < 0) &&
    542       (access(av[ac-2],R_OK|W_OK) < 0) )
    543   { fprintf(stderr,"%s: argument order has changed, it's now: str1 str2 files...\n",__progname);
    544   }
    545  str1 = av[1];
    546  str2 = av[2];
    547  av += 2;
    548  ac -= 2;
    549  s1l = strlen(str1);
    550  s2l = strlen(str2);
    551  if (s1l > BUF_SIZ)
    552   { fprintf(stderr,"%s: search string too long (max %d chars)\n",__progname,BUF_SIZ);
    553     exit(1);
    554   }
    555  tcgetattr(0,&orig_tio);
    556  signal(SIGTSTP,sigtstp);
    557  allfly = 0;
    558  cabove = 2;
    559  cbelow = 2;
    560  skip = 0;
    561  for (ac--,av++;ac;ac--,av++)
    562   { if (skip > 0)
    563      { skip --;
    564        continue;
    565      }
    566     if (**av == '-')
    567      { ++*av;
    568        if (!strcmp(*av,"debug"))
    569 	{ debugging ++;
    570 	}
    571        else if (!strcmp(*av,"w"))
    572 	{ wordmode = ! wordmode;
    573 	}
    574        else if ( (strcmp(*av,"!") == 0) ||
    575 		 (strcmp(*av,"go") == 0) ||
    576 		 (strcmp(*av,"noask") == 0) )
    577 	{ allfly = 1;
    578 	}
    579        else if ( (strcmp(*av,"nogo") == 0) ||
    580 		 (strcmp(*av,"ask") == 0) )
    581 	{ allfly = 0;
    582 	}
    583        else if (**av == 'c')
    584 	{ cabove = atoi(++*av);
    585 	  cbelow = cabove;
    586 	  limit_above_below();
    587 	}
    588        else if (**av == 'C')
    589 	{ ++*av;
    590 	  if (**av == 'A')
    591 	   { cabove = atoi(++*av);
    592 	     limit_above_below();
    593 	   }
    594 	  else if (**av == 'B')
    595 	   { cbelow = atoi(++*av);
    596 	     limit_above_below();
    597 	   }
    598 	  else
    599 	   { fprintf(stderr,"%s: -C must be -CA or -CB\n",__progname);
    600 	   }
    601 	}
    602        else if ( (strcmp(*av,"f") == 0) ||
    603 		 (strcmp(*av,"F") == 0) )
    604 	{ if (++skip >= ac)
    605 	   { fprintf(stderr,"%s: -%s what?\n",__progname,*av);
    606 	   }
    607 	  else
    608 	   { if (**av == 'f')
    609 	      { process_file(av[skip]);
    610 	      }
    611 	     else
    612 	      { process_indir_file(av[skip]);
    613 	      }
    614 	   }
    615 	}
    616      }
    617     else
    618      { process_file(*av);
    619      }
    620   }
    621  exit(0);
    622 }
    623