/*
** tab.c
**
** Expands and compresses tabs in text files.
**
** $Id: tab.c,v 2.1 94/08/09 17:17:48 GF Exp Locker: GF $
**
**************************************************************************
**
** Copyright © 1994 by Gabriele Falcioni. All Rights Reserved.
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** For inquiries, comments, donations, bug reports, write to:
**
** Gabriele Falcioni
** via E. Cialdini, 50
** I-60122 Ancona
** ITALY
**
**************************************************************************
**
** TAB: Tab processor. Expands and compresses tabs in text files.
**
** REQUIREMENTS
**
** · "exec.library"    V37+
** · "dos.library"     V37+
** · "utility.library" V37+
**
** · SAS DevSystem V6.x
** · Startup: "shellcres2.o"
** · Library: "filefinder.lib"
*/

#define _CMDNAME	"Tab"
#define _VER_REV	"2.1"
#define _CREATION	"(15.06.94)"
#define _TEMPLATE \
	"FILE/A/M,TO/A,ALL/S,TABS/K/N,BUFSIZE/K/N,COMPRESS/S" \
	",NOSQ=NOSQUOTES/S,NODQ=NODQUOTES/S,NOBS=NOBKSLASH/S" \
	",NOWARN/S,QUIET/S,CLONE/S,NOSTRINGS/S"

/*
 * COMMAND
 * Tab -- tab filtering utility.
 *
 * TEMPLATE
 * FILE/A/M,TO/A,ALL/S,TABS/K/N,BUFSIZE/K/N,COMPRESS/S,
 * NOSQ=NOSQUOTES/S,NODQ=NODQUOTES/S,NOBS=NOBKSLASH/S,
 * NOWARN/S,QUIET/S,CLONE/S,NOSTRINGS/S
 *
 * DESCRIPTION
 * This utility can expand tabs to spaces and vice versa in text files.
 * It is useful to change the tab step, eliminate tabs or compress spaces
 * in tabs to reduce file length. The tool is designed as a copier program,
 * much like the system's Copy command: like Copy, this tool offers multiple
 * sources handling, standard AmigaDOS patterns, ALL/S switch and a CLONE/S
 * switch to transfer the attributes, modification date and comments of
 * source files to the filtered ones.
 * This tool can be used without problems even on program source files: the
 * automaton used to compress spaces knows how to recognise (and leave
 * intact) language strings. Obviously, it's impossible to define a valid
 * string for every known language (at least, without an user-programmable
 * regular language recogniser): for simplicity, this tool considers strings
 * everything enclosed in single or double quotes. Backslashes are treated
 * as escape character either inside strings (to parse '\''-like cases) or
 * outside, for string continuation purposes (to parse backslash-linefeed
 * cases, where the string is continued in next line. This definition, of
 * course, leaves out some languages, for example Postscript (I hope nobody
 * would be interested in compressing spaces to tabs in a Postscript file),
 * but covers C, Rexx, Pascal, Prolog, 68k asm and others.
 * If ill-formed (or supposed so) strings are encountered, warning messages
 * will be printed to stdout to inform the user of the location and nature
 * of the problem. Note that this feature, like the string recogniser, can
 * be disabled at will.
 *
 * ARGUMENTS
 * FILE/A/M		- file(s) to process. Standard AmigaDOS patterns are accepted.
 * TO/A			- destination file or directory.
 * ALL/S			- search entire directories.
 * TABS/K/N		- tab step. Default is every 8 chars.
 * BUFSIZE/K/N	- size (in bytes) of I/O buffers. Default is 2048 bytes.
 * COMPRESS/S	- compress spaces with tabs. Default is to expand tabs with
 * 				  spaces.
 * NOSQUOTES/S	- ignore strings enclosed in single quotes.
 * NODQUOTES/S	- ignore strings enclosed in double quotes.
 * NOBKSLASH/S	- ignore backslashes (not treated as escape character).
 * NOWARN/S		- do not print warnings about unbalanced strings.
 * QUIET/S		- every message that is not an error is suppressed.
 * CLONE/S		- clone date, comment and attributes of source files.
 * NOSTRINGS/S	- a synonym for: NOSQUOTES NODQUOTES NOBKSLASH
 */

/* -------------------------------------------------------------------- */

#include <dos.h>
#include <string.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <utility/tagitem.h>
#include <dos/stdio.h>
#include <dos/dosasl.h>
#include <dos/rdargs.h>
#include <dos/dos.h>

#include <proto/dos.h>
#include <proto/exec.h>

#include <usrh/filefinder.h>

#include "tabcodec.h"

/* --- My favourite macros -------------------------------------------- */

#define STDMEM (MEMF_PUBLIC|MEMF_CLEAR)

#define ConStruct(type)		((type *)AllocMem(sizeof(type),STDMEM))
#define DiStruct(ptr)		FreeMem((ptr),sizeof(*(ptr)))
#define ClearStruct(ptr)	memset((ptr),0,sizeof(*(ptr)))

#ifndef FOREVER
#define FOREVER for (;;)
#endif

/* -------------------------------------------------------------------- */

/* a *slightly* nonstandard version string */
static UBYTE const EmbeddedVersionID[] = {
	"$VER: " _CMDNAME " " _VER_REV " " _CREATION
	" Copyright © 1994 by Gabriele Falcioni. FREE Software."
};

/* required by FileFinder and TabCoDec functions */
struct Library *UtilityBase;

/* appended to the stack based taglist */
static struct TagItem tcdc_tl_tail[] = {
	{ TAG_IGNORE },
	{ TAG_IGNORE },
	TAG_END
};

struct RDArgs *MyRdargs;
struct {
	UBYTE **file;
	UBYTE *dest;
	ULONG all;
	LONG  *tabstep;
	LONG  *bufsize;
	ULONG compress;
	ULONG nosq;
	ULONG nodq;
	ULONG nobs;
	ULONG nowarn;
	ULONG quiet;
	ULONG clone;
	ULONG nostr;
} MyArgs;

/* -------------------------------------------------------------------- */

#define INDENT_STEP 3

static LONG ASM trf_dh_entry(
	REG(a0) struct Hook *hk,
	REG(a2) struct FileInfoBlock *fib,
	REG(a1) LONG *msg)
{
	static LONG ntabs = 0;
	register LONG i;
	BPTR stdoutfh;

	putreg(REG_A4,(long)hk->h_Data);	/* *** RESTORE GLOBAL BASE *** */

	stdoutfh = Output();

	switch (*msg) {

		case TRFHOOK_ENTERNEWDIR:
			for (i=ntabs; i>=0; --i) (VOID)FPutC(stdoutfh,' ');
			ntabs += INDENT_STEP;
			Printf("%s [created]\n",fib->fib_FileName);
			break;

		case TRFHOOK_ENTEROLDDIR:
			for (i=ntabs; i>=0; --i) (VOID)FPutC(stdoutfh,' ');
			ntabs += INDENT_STEP;
			Printf("%s (Dir)\n",fib->fib_FileName);
			break;

		case TRFHOOK_OPENNEWFILE:
			for (i=ntabs; i>=0; --i) (VOID)FPutC(stdoutfh,' ');
			Printf("%s ... ",fib->fib_FileName);
			(VOID)Flush(stdoutfh);
			break;

		case TRFHOOK_LEAVEDIR:
			ntabs -= INDENT_STEP;
			break;

	}

	return 0;
}

static struct Hook displayhook = {
	{ NULL, NULL },
	(HOOKFUNC)trf_dh_entry,
};

/* -------------------------------------------------------------------- */

/*
 * this is from file: "shellcres2.a" (cres-like custom startup module)
 *
 * Features:
 * · Smaller than standard one
 * · Makes C programs resident-able
 * · Permits Shell execution only
 * · Initializes SysBase and DOSBase
 * · Requires V37+ ROMs
 * (of course practical *only* for really small Shell utilities)
 *
 */

void late_exit(long);

/*
 * custom exit function: void early_exit(long exitcode)
 * this function calls the "shellcres2.o" exit code
 */

void early_exit(long code)
{
	if (UtilityBase) CloseLibrary(UtilityBase);

	late_exit(code);
}

/*
 * custom main function: void early_main(void)
 * called by "shellcres2.o" (custom startup module, see above)
 */

void __stdargs early_main(void)
{
	LONG   errcode;
	UBYTE *errstr;

	struct TabProcEnv *tpe;
	struct TreeFilter *trf;

	if (!(UtilityBase = OpenLibrary("utility.library",37))) {
		Printf("Couldn't open \"%s\" V37+\n","utility.library");
		SetIoErr(ERROR_INVALID_RESIDENT_LIBRARY);
		early_exit(RETURN_FAIL);
	}

	displayhook.h_Data = (APTR)getreg(REG_A4);	/* *** BACKUP GLOBAL BASE *** */

	if (MyRdargs = ReadArgs(_TEMPLATE,(LONG *)&MyArgs,NULL)) {

		if (trf = CreateTreeFilter(
				TREEFT_Fromv,			MyArgs.file,
				TREEFT_To,				MyArgs.dest,
				TREEFT_All,				MyArgs.all,

				TREEFT_CopyProts,		MyArgs.clone,
				TREEFT_CopyComment,	MyArgs.clone,
				TREEFT_CopyDate,		MyArgs.clone,

				TREEFT_OpenFiles,		1L,
				TREEFT_DisplayHook,	(MyArgs.quiet ? NULL : &displayhook),
				TAG_END)) {

			if (MyArgs.tabstep) {
				tcdc_tl_tail[0].ti_Tag  = TCDCT_TabStep;
				tcdc_tl_tail[0].ti_Data = *MyArgs.tabstep;
			}

			if (MyArgs.bufsize) {
				tcdc_tl_tail[1].ti_Tag  = TCDCT_BufSize;
				tcdc_tl_tail[1].ti_Data = *MyArgs.bufsize;
			}

			if (MyArgs.nostr) {
				MyArgs.nosq = TRUE;
				MyArgs.nodq = TRUE;
				MyArgs.nobs = TRUE;
			}

			if (tpe = CreateTabProcEnv(
					TCDCT_NoSQuotes,	MyArgs.nosq,
					TCDCT_NoDQuotes,	MyArgs.nodq,
					TCDCT_NoBkSlash,	MyArgs.nobs,
					TCDCT_NoWarn,		MyArgs.nowarn,

					TAG_MORE,			tcdc_tl_tail)) {

				errcode = 0;
				errstr  = NULL;

				while (FilterTree(trf)) {
					BPTR infile,outfile;

					infile  = GetSourceFH(trf);
					outfile = GetTargetFH(trf);

					errcode = (
						MyArgs.compress
						? CompressTabs(tpe,infile,outfile)
						: ExpandTabs  (tpe,infile,outfile)
					);

					if (!MyArgs.quiet) {
						if (errcode) Printf("aborted.\n");
						else Printf("done.\n");
					}

					if (errcode) break;
				}

				DeleteTabProcEnv(tpe);
			} else {
				errcode = ERROR_NO_FREE_STORE;
				errstr  = "Couldn't allocate I/O buffers";
			}

			DeleteTreeFilter(trf);
		} else {
			errcode = ERROR_NO_FREE_STORE;
			errstr  = "Couldn't allocate matcher buffer";
		}

		FreeArgs(MyRdargs);
	} else {
		errcode = IoErr();
		errstr  = "Bad Args";
	}

	if (errcode) {
		PrintFault(errcode,errstr);
		early_exit(RETURN_FAIL);
	}

	early_exit(RETURN_OK);
}
