/* sh - JimG's version of sh to unshar files */
char *help = "\
Usage: sh [-v(012)] [-s(01)] [-o(01)] [filename]\n\
  -v = verbosity\n\
	0 = print nothing\n\
	1 = print anything not understood\n\
	2 = echo commands as they are executed\n\
  -s = safety\n\
	1 = exit when something is not understood\n";
char *help2 = "\
  -o = overwrite\n\
	1 = don't overwrite existing files\n\
  filename\n\
	read shar file from here (or stdin if missing)\n";

#define NDEBUG

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define ST_CMDLINE	1
#define ST_CAT		2
#define ST_SED		3

#define TST_OUT		1
#define TST_QUOTE	2
#define TST_ALPHA	3
#define TST_DIR		4

#define BUFSIZE 161
char buf1[BUFSIZE], buf2[BUFSIZE];
char *buff1end = buf1 + BUFSIZE;

unsigned short int verbosity = 1;
unsigned short int safety = 0;
unsigned short int overwrite = 1;

FILE *fopen(char*,char*);
void sh(FILE*);
int tokenize(char**,int,char*);
void bumpstr(char*);
void ind_error(char**,int);
void fileparse(char**,int,char*,FILE**);
int fillbuffer(char*,int,FILE*);
int match(char*,char*);
void ptokens(char**,int,FILE*);

int
fillbuffer(buf,len,fp)		/* This will read up to len-1 characters */
char *buf;
int len;
FILE *fp;
{
	register int i, c;
	
	for (i=1; i<len; i++) {
		c = getc(fp);
		if (c == EOF) break;
		*(buf++) = c;
		if (c == '\n') break;
	}
	*buf = 0;
	return (c);
}

main(argc,argv)
int argc;
char *argv[];
{
	register FILE *fp;
	register char *s;
	
	while ((--argc > 0) && ((*++argv)[0] == '-'))
	    for (s = argv[0]+1; *s != '\0'; s++)
		switch (*s) {
		case 'v':
			switch (s[1]) {
			case '0':
			case '1':
			case '2':
				verbosity = (*++s) - '0';
				break;
			default:
				verbosity = 1;
				break;
			}
			break;
		case 's':
			switch(s[1]) {
			case '0':
			case '1':
				safety = (*++s) - '0';
				break;
			default:
				safety = 1;
				break;
			}
			break;
		case 'o':
			switch(s[1]) {
			case '0':
			case '1':
				overwrite = (*++s) - '0';
				break;
			default:
				overwrite = 1;
				break;
			}
			break;
		default:
			fputs("sh: illegal option ",stderr);
			putc(*s,stderr);
			putc('\n',stderr);
			exit(10);
			break;
		}
	switch (argc) {
	case 0:	sh(stdin);
		break;
	case 1:	fp = fopen(argv[0],"r");
		if (fp == NULL) {
			fputs("Error - can't open file: ",stderr);
			fputs(argv[0],stderr);
			putc('\n',stderr);
			exit(10);
		}
		sh(fp);
		fclose(fp);
		break;
	default:
		fputs(help,stderr);
		fputs(help2,stderr);
		exit(5);
	}
	return (0);
}

int
match(pat,str)
register char *pat, *str;
{
	while (*pat != '\0')
		if (*pat++ != *str++) return (0);
	return(1);
}

void
sh(fp)
FILE *fp;
{
    int state = ST_CMDLINE;
    int oldc, c, tcnt;
    FILE *outfp;
    char *tokens[20];
    char schar;

    oldc = '\n';
    while ((c = fillbuffer(buf1, BUFSIZE, fp)) != EOF ) {
	switch (state) {
	case ST_CMDLINE:
		if (oldc != '\n') break;
		if (*buf1 == '#') break;
		tcnt = tokenize(tokens, 20, buf1);
		if (tcnt == 0) break;
		if (strcmp(tokens[0],"echo") == 0) {
			ptokens(tokens+1,tcnt-1,stdout);
		} else if (strcmp(tokens[0],"cat") == 0) {
			fileparse(tokens,tcnt,buf2,&outfp);
			if (*buf2) {
				state = ST_CAT;
				if (verbosity == 2) {
					fputs("I am executing:   ",stdout);
					ptokens(tokens,tcnt,stdout);
				}
			} else if (outfp != stdout) fclose(outfp);
		} else if (strcmp(tokens[0],"sed") == 0) {
			fileparse(tokens,tcnt,buf2,&outfp);
			if (*buf2) {
			    if (verbosity == 2) {
				fputs("I am executing:   ",stdout);
				ptokens(tokens,tcnt,stdout);
			    }
			    if ((tokens[1][0] == 's') &&
				(tokens[1][1] == tokens[1][4]) &&
				(tokens[1][2] == '^') &&
				(tokens[1][5] == tokens[1][1])) {
				    schar = tokens[1][3];
				    state = ST_SED;
			    } else {
				fputs("Warning -- unknown sed option\n",stderr);
				fputs(tokens[1],stderr);
				fputs("\nI am treating it as a 'cat'\n",stderr);
				fflush(stderr);
				state = ST_CAT;
			    }
			} else if (outfp != stdout) fclose(outfp);
		} else if (strcmp(tokens[0], "exit") == 0) {
			return;
		} else {
			if (verbosity > 0) {
/*			  fputs("I don't understand and am ignoring:\n",stdout);*/
			  fputs("Don't understand: ",stdout);
			  ptokens(tokens,tcnt,stdout);
			}
			if (safety == 1) exit(5);
		}
		break;
	case ST_CAT:
		if ((oldc == '\n') && (match(buf2,buf1))) {
			state = ST_CMDLINE;
			if (outfp != stdout) fclose(outfp);
		} else fputs(buf1,outfp);
		break;
	case ST_SED:
		if ((oldc == '\n') && (match(buf2,buf1))) {
			state = ST_CMDLINE;
			if (outfp != stdout) fclose(outfp);
		} else if ((oldc == '\n') && (*buf1 == schar))
			fputs(buf1+1,outfp);
		else fputs(buf1,outfp);
		break;
	default:
		fputs("\nInternal Error -- unknown state!\n",stderr);
		exit(20);
	}
	oldc = c;
    }
}

void
ptokens(tokens, size, fp)
char *tokens[];
int size;
FILE *fp;
{
	register int i;
	
	for (i = 0; i < (size - 1); i++) {
		fputs(tokens[i],fp);
		putc(' ',fp);
	}
	if (size > 0) fputs(tokens[size - 1],fp);
	putc('\n',fp);
	fflush(fp);
}

int
tokenize(tokens, tsize, buf)
char *tokens[];
int tsize;
char *buf;
{
	register char c;
	char quote;
	int tcnt;
	char *tbuf;
	int state;

	tcnt = 0;
	state = TST_OUT;
	for (; ((c = *buf) != NULL); buf++) {
	    switch (state) {
	    case TST_OUT:
		switch (c) {
		case ' ':
		case '\t':
		case '\n':
			break;
		case '\'':
		case '"':
		case '`':
			quote = c;
			state = TST_QUOTE;
			if (tcnt >= tsize) return(tsize);
			tokens[tcnt++] = tbuf = (buf + 1);
			tbuf++;
			break;
		case '<':
		case '>':
			state = TST_DIR;
			if (tcnt >= tsize) return(tsize);
			tokens[tcnt++] = tbuf = buf;
			tbuf++;
			break;
		case '\\':
			buf++;
		default:
			state = TST_ALPHA;
			if (tcnt >= tsize) return(tsize);
			tokens[tcnt++] = tbuf = buf;
			tbuf++;
			break;
		} /* end of switch (c) */
		break;
	    case TST_QUOTE:
		if (c == quote) {
			*buf = 0;
			state = TST_OUT;
		} else {
/*			*tbuf++ = c; */		/* Currently no-op */
		}
		break;
	    case TST_DIR:
		if ((c != '>') && (c != '<')) {
			if ((c != ' ') &&
			    (c != '\t') &&
			    (c != '\n')) bumpstr(buf);
			*buf = 0;
			state = TST_OUT;
		} else {
/*			*tbuf++ = c; */		/* Currently no-op */
		}
		break;
	    case TST_ALPHA:
		if ((c == ' ') ||
		    (c == '\t') ||
		    (c == '\n')) {
			*tbuf = 0;
			state = TST_OUT;
		} else if (c != '\\') {
			*tbuf++ = c;
/*			*tbuf++ = c; */		/* Currently no-op */
		}
		break;
	    } /* end switch (state) */
	}
	return (tcnt);
}

void
bumpstr(buff)		/* buff has to be a pointer in buf1 !!!!! */
register char *buff;
{
	register char c1, c2;
	
	assert(((buff >= buf1) && (buff < buff1end)));

	c1 = *buff++;
	while (buff != buff1end) {
		c2 = *buff;
		*buff++ = c1;
		if ((buff == buff1end) || (c1 == 0)) break;
		c1 = *buff;
		*buff++ = c2;
		if (c2 == 0) break;
	}
}

void
ind_error(tokens, tcnt)
char *tokens[];
int tcnt;
{
	fputs("Error in indirection -- ignored\n",stderr);
	ptokens(tokens,tcnt,stderr);
}

void
fileparse(tokens, tcnt, buf, outfp)
char *tokens[];
int tcnt;
char *buf;
FILE **outfp;
{
	int i;

	*buf = 0;
	*outfp = NULL;
	
	for (i=0; i < tcnt; i++) {
	    if (strcmp(">",tokens[i])==0) {
		if (*outfp) ind_error(tokens,tcnt);
		else if (++i == tcnt) ind_error(tokens,tcnt);
		else {
			if (overwrite == 1) {
			    *outfp = fopen(tokens[i],"r");
			    if (*outfp != NULL) {
				fputs("Warning: will not overwrite: ",stderr);
				fputs(tokens[i],stderr);
				putc('\n',stderr);
				fflush(stderr);
				fclose(*outfp);
				tokens[i]="nil:";
			    }
			}
			*outfp = fopen(tokens[i],"w");
			if (*outfp == NULL) ind_error(tokens,tcnt);
		}
	    } else if (strcmp(">>",tokens[i])==0) {
		if (*outfp) ind_error(tokens,tcnt);
		else if (++i == tcnt) ind_error(tokens,tcnt);
		else {
			*outfp = fopen(tokens[i],"a");
			if (*outfp == NULL) ind_error(tokens,tcnt);
		}
	    } else if (strcmp("<<",tokens[i])==0) {
		if (*buf) ind_error(tokens,tcnt);
		else if (++i == tcnt) ind_error(tokens,tcnt);
		else strcpy(buf,tokens[i]);
	    }
	} /* end for */
	if (*outfp == NULL) *outfp = stdout;
}

