#ifdef	ournix
#include "ournix.h"
#endif
char sccsID[] = "@(#) tab.c V0.4 Copyright http://www.berklix.com (pjc+jhs) 1988\n";

/* tab - entab + detab, convert spaces to tabs, or inverse.

 BUG:	tab.c does not recognise epson or hp pcl escape sequences are invisible.
	thus spacing of tab stops becomes miscalculated.
	mktemp & verbose added by jhs.
	jhs did:
		reformatted with `indent', then manually
		added typedefs & more meaningfull names
	I think jhs did signal handling
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#ifdef	unix
#include <sys/file.h>
#include <sys/types.h>
#else
#include <sys/types.h>
#include <fcntl.h>
#endif
#ifdef	i386	/* { */
#include <sys/fcntl.h>
#endif	/* } */

#ifndef	O_BINARY
#define	O_BINARY	0
#endif

typedef	int	FD ;
typedef char FLAG;

FLAG		verbose = 0;
char		**ARGV;

#ifdef scs	/* { */
extern char    *
mktemp();
#define TMP_NAME "taXXXXXX"
#else	/* }{ */
#include <unistd.h>
#ifdef MSDOS	/* { */
#define TMP_NAME "taXXXXXX"
#else	/* }{ */
#define TMP_NAME "tab.XXXXXX"
#endif	/* } */
#endif	/* } */

#include <sys/param.h>	/* for MAXPATHLEN */
char	tmp_name[MAXPATHLEN];	/* temporary file name */


#ifdef	MSDOS
#define MAXBUF	25000
#else
#define MAXBUF	250000
#endif

/* output/tmpfile variables */
char            out_buf[MAXBUF];
int             tmp_fd;
char           *out_p = out_buf;

/* input file variables */
unsigned char   in_buf[MAXBUF];
unsigned char  *in_end;
unsigned char  *in_p;

void clean_unlink(unlink_f)
	FLAG	unlink_f ;
	{
	if (tmp_fd > 0)
		{
		close(tmp_fd);
		tmp_fd = 0;
		if (unlink_f) unlink(tmp_name);
		}
	out_p = out_buf;
	}

void 
clean_on_error()
	{
	clean_unlink((FLAG)1);
	exit(2);
	}

void fls_tmp()
	{
	int             diff, res;
	if (tmp_fd == 0) return;
	diff = out_p - out_buf;
	if (diff != 0) res = write(tmp_fd, out_buf, diff);
	out_p = out_buf;
	if (diff != res)
		{
		fprintf(stderr, "Write error on temporary file\n");
		close(tmp_fd);
		unlink(tmp_name);
		exit(1);
		}
	lseek(tmp_fd, (off_t) 0, 0);	/* go back to beginning */
	}

void copyback(name,fd)
	char	*name ;
	FD      fd;
	{
	unsigned        diff;
	int             res = 1;
	if (tmp_fd == 0)
		{	/* no temporary file */
		diff = out_p - out_buf;
		out_p = out_buf;
		if (diff && write(fd, out_buf, diff) != diff) res = -1;
		}
	else	{
		while ((diff = read(tmp_fd, out_buf, MAXBUF)) > 0)
			if (write(fd, out_buf, diff) != diff)
				{ res = -1; break; }
		}
	if (res < 0)
		{
		fprintf(stderr, "Write failure on source file\n");
		clean_unlink((FLAG)0);
		fprintf(stderr,
			"Suggest you manually rescue data from %s for %s\n",
			tmp_name,name);
		exit(2);
		}
	close(fd);
	}

void out_c(c)
	{
	int             diff;
	if (tmp_fd == 0)
		{
		/* no temporary file yet */
		tmp_fd = open(tmp_name,
			O_BINARY | O_CREAT | O_TRUNC | O_RDWR, 0200);
		if (tmp_fd < 0)
			{
			fprintf(stderr, "Cannot create temporary file '%s'\n",
				tmp_name);
			exit(3);
			}
		}
	diff = out_p - out_buf;
	out_p = out_buf;
	if (write(tmp_fd, out_buf, diff) != diff)
		{
		fprintf(stderr, "Write failure on temporary file\n");
		clean_unlink((FLAG)1);
		exit(4);
		}
	*out_p++ = c;
	}

int in_c(fd)
	FD             fd;
	{
	int             diff;
	diff = read(fd, in_buf, MAXBUF);
	if (diff <= 0) return (EOF);
	in_end = in_buf + diff;
	in_p = in_buf;
	return (*in_p++);
	}

#define IN_C(fd) ((in_p < in_end) ? *in_p++ : in_c(fd))
#define OUTC(ch) ((out_p < &out_buf[MAXBUF] )? *out_p++ = ch : out_c(ch))

/* actually do the entabing */
void entab(fd)
	FD             fd;
	{
	register int    c;
	register int    col, newcol;
	FLAG            quoted = 0;
	/*- 1 if a quote character is seen in line,
	if you entabulate a quoted string in a program,
	it will change the binary, so once double quote
	is detected on a line, no more tabbing occurs,
	lines such as
		printf("very_long_line\
		spaces		end");
	are not protected from change
	Also to be set to 1 if an escape character is
	encountered in the line, to preserve tab spacing
	in text with printer control escape sequences
	*/
	col = 0;
	for (;;)
		{
		newcol = col;
		while ((c = IN_C(fd)) == ' ' || c == '\t')
			{
			if (c == '\t')
				{
				newcol = (newcol + 8) & ~07;
				OUTC('\t');
				col = newcol;
				continue;
				}
			newcol++;
			if ((newcol % 8) == 0)
				{
				if (newcol - col > 1) OUTC('\t');
				else OUTC(' ');
				col = newcol;
				}
			}
		while (col < newcol) { OUTC(' '); col++; }
		if (c == EOF) return;
		OUTC(c);
#ifdef	MSDOS
		if (c == '\r') continue;
#endif
		if (c == '\n') col = 0; else col++;
		}
	}

/* detab the file */
void detab(fd)
	FD             fd;
	{
	register int    c;
	register int    column = 0;
	/* left most char on page is column no. 0 tab positions are normally
	   0,8,16 etc */
	while ((c = IN_C(fd)) != EOF)
		{
		switch (c)
			{
			default:
				if (c >= ' ' && c <= '~') column++;
				break;
			case '\r':
			case '\n':
			case '\f':
				/* assume FF puts cursor to left margin */
				column = 0;
				break;
			case '\t':
				do { OUTC(' '); } while ((++column % 8) != 0);
				continue;
			case '\b':
				if (column > 0) column--;
				break;
			}
		OUTC(c);
		}
	}

int main(argc, argv)
	char          **argv;
	{
	FD              fd;
	void            clean_on_error();
	int             dir = 0;

	ARGV = argv;
#ifdef	VSL	/* { */
#include	"../../include/vsl.h"
#endif	/* } */
	if (argc < 3)
		{
		fprintf(stderr, "Usage: tab -d/-e [files]\n");
		exit(1);
		}
	strcpy(tmp_name, TMP_NAME);
	signal(SIGINT, clean_on_error);
#ifdef scs
	(void) mktemp(tmp_name);
#else
	if (mktemp(tmp_name) != tmp_name)
		fprintf(stderr, "%s: mktemp failed\n", *ARGV);
	/* else fprintf(stderr,"%s: mktemp ok\n",*ARGV); */
#endif
	for (argv++; *argv; argv++)
		{
		if (**argv == '-')
			{
			if (dir != 0)
				{
				fprintf(stderr, "En/detab selected - ignored\n");
				continue;
				}
			switch ((*argv)[1])
				{
				case 'd':	/* detabulate */
				case 'D':
					dir = -1; break;
				case 'e':	/* entabulate */
				case 'E':
					dir = 1; break;
				case 'v':
					verbose = 1; break;
				default:
					fprintf(stderr, "Unknown option - ignored\n");
					break;
				}
			continue;
			}
		if (dir == 0)
			{
			fprintf(stderr, "en/detab not set - cannot continue\n");
			exit(2);
			}
		fd = open(*argv, O_BINARY | O_RDONLY);
		if (fd < 0)
			{
			fprintf(stderr, "Cannot open '%s'\n", *argv);
			continue;
			}
		if (verbose)
			printf("%s\n", *argv);
		if (dir > 0)
			entab(fd);
		else
			detab(fd);
		close(fd);
		fls_tmp();
		signal(SIGINT, SIG_IGN);
		fd = open(*argv, O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, 0200);
		if (fd < 0)
			{
			signal(SIGINT, clean_on_error);
			fprintf(stderr, "Cannot recreate '%s'\n", *argv);
			clean_unlink((FLAG)1);
			continue;
			}
		copyback(*argv,fd);
		signal(SIGINT, clean_on_error);
		clean_unlink((FLAG)1);
		}
	exit(0);
	}
