[comp.sources.misc] v04i011: curly/uncurly: fold and unfold file names into csh {} form

ksb@s.cc.purdue.edu (Kevin Braunsdorf) (07/31/88)

Posting-number: Volume 4, Issue 11
Submitted-by: "Kevin Braunsdorf" <ksb@s.cc.purdue.edu>
Archive-name: curly

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	curly.c
#	uncurly.c
# This archive created: Sat Jul 30 18:57:01 1988
# By:	Kevin Braunsdorf (Purdue UNIX Group)
sed 's/^K//' << \SHAR_EOF > README
KHere are two programs I have found (recently) to be quite useful.
KOne of them expands the C-Shell curly braces notation for file name
Kbuilding, the other compresses a list of file names into one of
Kthese expressions.  I called these programs `curly' and `uncurly'.
K
KThere is am example usage of {un,}curly in the comments in the code,
Kcopied below.  I have used them to compress a dictionary (one initial
Kletter at a time BTW), and to compress lists of files on tape.
K
KOne might pipe the output of a find to uncurly and compress to store
Ka list of filenames as:
K
K	$ find . -type f -print | uncurly | compress > /tmp/files.u.Z
K
Kthen (later on) we need that list of files again...
K
K	$ zcat /tmp/files.u.Z | curly | xargs do_something
K
Kthis yields substantial compression over just compress alone, which it
Kshouldn't.  (We know something quite special about the output of find
Kon a `normal' find output that gives us an advantage here.)
K
KI always name the output from uncurly as files.u.  Here is some sample
Kcompression data (for 567 handy filenames in my src directory):
K
K  15 -rw-r-----  1 ksb         14435 Jul 30 17:27 files		# 100%
K   5 -rw-r-----  1 ksb          4754 Jul 30 17:27 files.Z	# 32.9%
K   5 -rw-r-----  1 ksb          5074 Jul 30 17:26 files.u	# 35.2%
K   3 -rw-r-----  1 ksb          2810 Jul 30 17:27 files.u.Z	# 17.3%
K
KI would like to be mailed any bug reports so I can fix my own copy.
K
KKnown bugs:  doesn't handle files with `{' or `}' in them well.
K
KEnjoy.
Kkayessbee (Kevin S Braunsdorf, ksb@j.cc.purdue.edu, pur-ee!ksb)
SHAR_EOF
sed 's/^K//' << \SHAR_EOF > curly.c
K/*
K * curly -- expand {...} as csh(1)					(ksb)
K *
K * Copyright 1988, All Rights Reserved
K *	Kevin S Braunsdorf
K *	ksb@j.cc.purdue.edu, pur-ee!ksb
K *	Math/Sci Building, Purdue Univ
K *	West Lafayette, IN
K *
K *  `You may redistibute this code as long as you don't claim to have
K *   written it. -- ksb'
K *
K * We are limited to not eating backslash escapes because that would be
K * very confusing to the user.  If you need a file name {a}.c don't call
K * this routine.  Simple.  (If we did use \ as special, then \.c would
K * need to be quoted from us... it never ends, so we let the shells worry
K * about \ quoting for us.)
K *
K * We don't expand other globbing characters, because ksh will expand
K * those for us when it reads our output in `quotes`.
K *
K * The command
K *	$ curly c{1,2,3,4,5}.c
K * outputs
K *	c1.c c2.c c3.c c4.c c5.c
K *
K * So use you might use
K *	$ tar xv `curly c{1,2,3,4,5}.c`
K * to extract them from tape.
K *
K * If we are given no arguments we can read stdin for strings to glob.
K * The READSTDIN switch controls this feature.
K *
K * Improvments:
K *
K * This code could be mixed with other globbing code to fully emulate
K * csh(1) globbing in a few minutes,  I have no need of this (yet).
K *
K * We can avoid the malloc/strcpy/strcat in DoExpr if we build right
K * context right to left in a large buffer; this buffer could limit the
K * size of the glob expression, but other factors limit it already.
K *
K * $Compile: ${CC-cc} ${DEBUG--O} ${SYS--Dbsd} -DREADSTDIN %f -o %F
K * $Compile: ${CC-cc} ${DEBUG--O} ${SYS--Dbsd} %f -o %F
K * $Lint: lint -abhxp ${SYS--Dbsd} -DREADSTDIN %f
K * $Lint: lint -abhxp ${SYS--Dbsd} %f
K */
K#include <stdio.h>
K#include <sys/param.h>
K#include <sys/types.h>
K
Kstatic char *progname =
K	"$Id: curly.c,v 2.0 88/07/30 17:10:38 ksb Exp $";
K
K/*
K * If your compiler doesn't allow `register' as a parameter storage class
K * define PREG as empty, and don't worry about it.
K */
K#define PREG	register	/* make arguments faster access		*/
K/* #define PREG			/* no register arguments		*/
K
K#if defined(bsd)
K#define strrchr rindex		/* I must be on bsd, us rindex		*/
K#endif
K
K#if !defined(MAXPATHLEN)
K#define MAXPATHLEN	1024
K#endif
K
Kextern char *malloc(), *realloc(), *strcpy();
K
K/* static int iMatch = 0; 	*/
Kstatic char acName[MAXPATHLEN];
Kextern void DoExpr(), DoList();
K
K#if defined(READSTDIN)
K#define FIRST_GUESS	8	/* be get on MAXPATHLEN * this		*/
K#define NEXT_GUESS	2	/* we hedge with MAXPATHLEN * this	*/
K#define GRAB		2	/* size chunk to read (<= NEXT_GUESS)	*/
K
Kstatic char acNoMem[] = "%s: out of memory\n";
K
K/*
K * Here we call gets() to read a glob expression to do.			(ksb)
K * Repeat until end of file.
K */
Kvoid
KDoStdin(pcAccum)
KPREG char *pcAccum;
K{
K	extern char *strrchr();
K	auto char acLine[MAXPATHLEN*GRAB];
K	static char *pcLine = (char *)0;
K	static unsigned uBufLen = 0;
K	register unsigned uPos;
K	register char *pcNewLine;
K
K	acLine[MAXPATHLEN*GRAB-1] = '\000';
K	if ((char *)0 == pcLine) {
K		uBufLen = MAXPATHLEN*FIRST_GUESS;
K		pcLine = malloc(uBufLen);
K		if ((char *)0 == pcLine) {
K			fprintf(stderr, acNoMem, progname);
K			exit(1);
K		}
K	}
K	uPos = 0;
K	while (NULL != fgets(acLine, MAXPATHLEN*GRAB-1, stdin)) {
K		pcNewLine = strrchr(acLine, '\n');
K		if (0 == uPos && (char *)0 != pcNewLine) {
K			*pcNewLine = '\000';
K			DoExpr(pcAccum, acLine, "\n");
K			continue;
K		}
K		if ((char *)0 != pcNewLine) {
K			*pcNewLine = '\000';
K		}
K		if (uPos + MAXPATHLEN*GRAB-1 > uBufLen) {
K			uBufLen += MAXPATHLEN*NEXT_GUESS;
K			pcLine = realloc(pcLine, uBufLen);
K		}
K		strcpy(pcLine+uPos, acLine);
K		if ((char *)0 == pcNewLine) {	/* we got chars, no end yet */
K			uPos += MAXPATHLEN*GRAB-2;
K			continue;
K		}
K		/* we have a line */
K		DoExpr(pcAccum, pcLine, "\n");
K		uPos = 0;
K	}
K}
K#endif	/* we can read stdin for a list of patterns */
K
K/*
K * find a matching close char for the open we just ate, or (char *)0	(ksb)
K *	pc = FindMatch("test(a,b))+f(d)", '(', ')', 1);
K *			         ^ pc points here
K */
Kchar *
KFindMatch(pcBuf, cOpen, cClose, iLevel)
Kchar *pcBuf;
Kchar cOpen, cClose;
Kint iLevel;
K{
K	while ('\000' != *pcBuf) {
K		if (cClose == *pcBuf) {
K			--iLevel;
K		} else if (cOpen == *pcBuf) {
K			++iLevel;
K		}
K		if (0 == iLevel)
K			return pcBuf;
K		++pcBuf;
K	}
K	return (char *)0;
K}
K
K/*
K * if we can locate a curly expression in our expression if the form:	(ksb)
K *	 	left { list } right
K *	1) copy left side to pcAccum,
K *	2) add right to our right context (malloc a new buffer if needed)
K *	3) call DoList(pcAccum, list, right)
K * or if we find no such curly expression
K *	1) copy all nonspecial chars to pcAccum
K *	2) recurse with DoExpr(pcAccum, pcRight, "")
K */
Kvoid
KDoExpr(pcAccum, pcExpr, pcRight)
KPREG char *pcAccum;
Kchar *pcExpr, *pcRight;
K{
K	extern void DoList();
K	extern char *malloc(), *strcat(), *strcpy();
K	register char *pcClose;
K	register char *pcComma;
K	register char *pcTemp;
K	register unsigned int uLen;
K
K	while ('{' != *pcExpr && '\000' != *pcExpr) {	/*}*/
K		*pcAccum++ = *pcExpr++;
K	}
K
K	switch (*pcExpr) {
K	case '\000':
K		if (*pcRight == '\000') {	/* no right context	*/
K			if (pcAccum != acName) {
K				*pcAccum = '\000';
K				fputs(acName, stdout);
K				/* ++iMatch; */
K			}
K		} else {
K			DoExpr(pcAccum, pcRight, "");
K		}
K		break;
K	case '{':
K		pcClose = FindMatch(pcExpr, '{', '}', 0);
K		/*
K		 * if an open is unbalanced we ignore it.
K		 */
K		if ((char *)0 == pcClose) {
K			*pcAccum++ = *pcExpr++;
K			DoExpr(pcAccum, pcExpr, pcRight);
K			break;
K		}
K		*pcClose++ = '\000';
K		pcComma = pcExpr+1;
K
K		/*
K		 * Now that the expr is cracked we can optimize if the
K		 * additional right context is empty.  If it is not we
K		 * have to compute a new right context.
K		 */
K		uLen = strlen(pcClose);
K		if (0 == uLen) {
K			DoList(pcAccum, pcComma, pcRight);
K		} else {
K			uLen += strlen(pcRight);
K			pcTemp = malloc(uLen+1);
K			(void) strcpy(pcTemp, pcClose);
K			(void) strcat(pcTemp, pcRight);
K			DoList(pcAccum, pcComma, pcTemp);
K			free(pcTemp);
K		}
K		*--pcClose = '}';
K		break;
K	}
K}
K
K/*
K * do a comma separated list of terms with known right context		(ksb)
K *	1) loop through exprs at this level
K *	2) call DoExpr(pcAccum, SubExpr, Right)
K */
Kvoid
KDoList(pcAccum, pcList, pcRight)
KPREG char *pcAccum;
Kchar *pcList, *pcRight;
K{
K	extern void DoExpr();
K	register char *pcThis;
K	register int iLevel;
K
K	iLevel = 0;
K
K	for (pcThis = pcList; '\000' != *pcList; ++pcList) {
K		switch (*pcList) {
K		case '{':
K			++iLevel;
K			break;
K		case '}':
K			--iLevel;
K			break;
K		default:
K			break;
K		case ',':
K			if (0 == iLevel) {
K				*pcList = '\000';
K				DoExpr(pcAccum, pcThis, pcRight);
K				*pcList = ',';
K				pcThis = pcList+1;
K			}
K			break;
K		}
K	}
K	DoExpr(pcAccum, pcThis, pcRight);
K}
K
K/*
K * Special case "{}" as csh(1) does for find (YUCK!)			(ksb)
K * We take no options so that they won't conflict with anything.
K * Count option exprs so we can output a blank line if we come up empty
K * (I've forgotten why we do this...)
K */
Kint
Kmain(argc, argv)
Kint argc;
Kchar **argv;
K{
K	register char *pcPat;
K
K	progname = *argv++;
K	--argc;
K
K#if defined(READSTDIN)
K	if (0 == argc) {
K		DoStdin(acName);
K	}
K#endif
K	while (argc > 0) {
K		pcPat = *argv++;
K		--argc;
K		/*
K		 * this kludge keeps us more csh(1) compatible
K		 */
K		if ('{' == pcPat[0] && '}' == pcPat[1] && '\000' == pcPat[2]) {
K			fputs("{}\n", stdout);
K			/* ++iMatch; */
K			continue;
K		}
K		DoExpr(acName, pcPat, "\n");
K	}
K
K	exit(0);
K}
SHAR_EOF
sed 's/^K//' << \SHAR_EOF > uncurly.c
K/*
K * unculry -- uncurly expand a list of parameters			(ksb)
K *
K * Copyright 1988, All Rights Reserved
K *	Kevin S Braunsdorf
K *	ksb@j.cc.purdue.edu, pur-ee!ksb
K *	Math/Sci Building, Purdue Univ
K *	West Lafayette, IN
K *
K *  `You may redistibute this code as long as you don't claim to have
K *   written it. -- ksb'
K *
K * The command
K *	$ uncurly c1.c c2.c c3.c c4.c c5.c
K * outputs
K *	c{1,2,3,4,5}.c
K *
K * So one might pipe the ouptut of a find to uncurly to compress the filenames
K * like:
K *	$ find . -type f -print | uncurly | compress > /tmp/${USER}files.Z
K *	# later on we need the list again...
K *	$ zcat /tmp/${USER}files.Z | curly | xargs do_something
K *
K * Improvments:
K *
K * This code could be mixed with other globbing code to fully emulate
K * an `arcglob' function, however this assumes the files exist in there
K * present form and is therefore less useful (to me).
K *
K * We could free more memory, if we were more carefull with our bookkeeping.
K *
K * The READSTDIN flag could be stired with the code for main to get something
K * that allocate less memory before UnCulry was called, free'd it and went
K * back to reading... if you run out of memory you can try it and send me
K * a patch :-).
K *
K * $Compile: ${CC-cc} ${DEBUG--O} ${SYS--Dbsd} -DREADSTDIN %f -o %F
K * $Compile: ${CC-cc} ${DEBUG--O} ${SYS--Dbsd} %f -o %F
K * $Lint: lint -abhxp ${SYS--Dbsd} -DREADSTDIN %f
K * $Lint: lint -abhxp ${SYS--Dbsd} %f
K */
K#include <stdio.h>
K#include <sys/param.h>
K#include <sys/types.h>
K
Kstatic char *progname =
K	"$Id: uncurly.c,v 2.0 88/07/30 17:10:50 ksb Exp $";
K
K/*
K * If your compiler doesn't allow `register' as a parameter storage class
K * define PREG as empty, and don't worry about it.
K */
K#define PREG	register	/* make arguments faster access		*/
K/* #define PREG			/* no register arguments		*/
K
K#if defined(bsd)
K#define strrchr rindex		/* I must be on bsd, us rindex		*/
K#endif
K
K#if !defined(MAXPATHLEN)
K#define MAXPATHLEN	1024
K#endif
K
Kextern char *malloc(), *calloc(), *strrchr(), *strcat();
Kstatic char acNoMem[] = "%s: out of memory\n";
K
K/*
K * find a matching close char for the open we just ate, or (char *)0	(ksb)
K *	pc = FindMatch("test(a,b))+f(d)", '(', ')', 1);
K *			         ^ pc points here
K */
Kchar *
KFindMatch(pcBuf, cOpen, cClose, iLevel)
KPREG char *pcBuf;
Kchar cOpen, cClose;
Kint iLevel;
K{
K	while ('\000' != *pcBuf) {
K		if (cClose == *pcBuf) {
K			--iLevel;
K		} else if (cOpen == *pcBuf) {
K			++iLevel;
K		}
K		if (0 == iLevel)
K			return pcBuf;
K		++pcBuf;
K	}
K	return (char *)0;
K}
K
K/*
K * save a string in malloc space					(ksb)
K */
Kchar *
Kstrsave(pc)
Kchar *pc;
K{
K	extern char *strcpy();
K	extern int strlen();
K	register char *pcMem;
K
K	pcMem = malloc((unsigned int) strlen(pc)+1);
K	if ((char *)0 == pcMem) {
K		fprintf(stderr, acNoMem, progname);
K		exit(1);
K	}
K	return strcpy(pcMem, pc);
K}
K
K#if defined(READSTDIN)
K#define FIRST_GUESS	8192	/* initial number of input files	*/
K#define NEXT_GUESS	2048	/* add this many if too few		*/
K
K/*
K * Joe wants us to turn a piped list of files into a big glob list	(ksb)
K * we return the number of files (he gave us) and a vector of them.
K */
Kunsigned int
KGetFiles(pppcArgv)
Kchar ***pppcArgv;
K{
K	extern char *realloc();
K	register unsigned int uCount, uLeft;
K	register char **ppcVector;
K	auto char acFile[MAXPATHLEN];
K
K	ppcVector = (char **) calloc(FIRST_GUESS, sizeof(char *));
K	uCount = 0;
K	uLeft = FIRST_GUESS;
K	while (NULL != gets(acFile)) {
K		if (0 == uLeft) {
K			uLeft = (uCount+NEXT_GUESS) * sizeof(char *);
K			ppcVector = (char **) realloc((char *)ppcVector, uLeft);
K			uLeft = NEXT_GUESS;
K		}
K		ppcVector[uCount] = strsave(acFile);
K		++uCount;
K		--uLeft;
K	}
K
K	*pppcArgv = ppcVector;
K	return uCount;
K}
K#endif	/* find files from stdin	*/
K
K/*
K * longest common prefix of more than one string			(ksb)
K * Note that the prefix must have balanced '{'..'}' in it.
K */
Kint
KPrefix(n, ppcList, puiLen)
Kunsigned int n;
Kchar **ppcList;
Kunsigned *puiLen;
K{
K	register int cCmp, cCur, iBal;
K	auto unsigned int j, i, uArea, uLen, uSpan, uCurlen;
K
K	*puiLen = 0;
K
K	iBal = 0;
K	for (j = 0; j < n; ++j) {
K		if ('\000' == ppcList[j][0]) {
K			break;
K		}
K	}
K
K	/* trivial case either first or second sring is empty
K	 */
K	if (j < 2) {
K		return 0;
K	}
K
K	uCurlen = uArea = uLen = uSpan = 0;
K	while ('\000' != (cCur = ppcList[0][uCurlen])) {
K		if ('{' == cCur)
K			++iBal;
K		else if ('}' == cCur)
K			--iBal;
K		for (i = 1; i < j; ++i) {
K			cCmp = ppcList[i][uCurlen];
K			if ('\000' == cCmp || cCur != cCmp) {
K				j = i;
K				break;
K			}
K		}
K		++uCurlen;
K		if (0 == iBal && uCurlen * j > uArea) {
K			uArea = uCurlen*j;
K			uLen = uCurlen;
K			uSpan = j;
K		}
K	}
K	*puiLen = uLen;
K	return uSpan;
K}
K
K/*
K * longest common suffix of more than one string			(ksb)
K *  1) find the ends of all the strings
K *  2) back off until we find a non-match, but keep looking
K *  3) return the one with most characters in it
K * Note that the suffix must have balanced '{'..'}' in it.
K */
Kint
KSuffix(n, ppcList, puiLen)
Kunsigned int n;
Kchar **ppcList;
Kunsigned *puiLen;
K{
K	register char **ppcLast, *pcTemp;
K	register unsigned int j, i, uCurlen;
K	auto unsigned uArea, uLen, uSpan, iStopAt;
K	auto int cCur, iBal;
K
K	*puiLen = 0;
K
K	ppcLast = (char **)calloc(n, sizeof(char *));
K	if ((char **)0 == ppcLast) {
K		fprintf(stderr, acNoMem, progname);
K		exit(1);
K	}
K	for (j = 0; j < n; ++j) {
K		ppcLast[j] = strrchr(ppcList[j], '\000');
K		if (ppcLast[j] == ppcList[j]) {
K			break;
K		}
K	}
K
K	iBal = uCurlen = uArea = uLen = uSpan = 0;
K	while (ppcLast[0] != ppcList[0]) {
K		cCur = ppcLast[0][-1];
K		if ('{' == cCur)
K			++iBal;
K		else if ('}' == cCur)
K			--iBal;
K		iStopAt = -1;
K		for (i = 0; i < j; ++i) {
K			pcTemp = --ppcLast[i];
K			if (cCur != pcTemp[0]) {
K				j = i;
K				break;
K			}
K			if (ppcList[i] == pcTemp && -1 == iStopAt) {
K				iStopAt = i;
K			}
K		}
K		++uCurlen;
K		if (0 == iBal && uCurlen * j > uArea) {
K			uArea = uCurlen*j;
K			uLen = uCurlen;
K			uSpan = j;
K		}
K		if (-1 != iStopAt) {
K			j = iStopAt;
K		}
K	}
K	*puiLen = uLen;
K	free((char *)ppcLast);
K	return uSpan;
K}
K
K/*
K * determine context for a list ppcList[0..n-1]				(ksb)
K *	left { ... } right
K *
K * If the longest common prefix will eat more character then
K * we should use that, else try the longest common suffix.
K * If both are 0 chars just return the list (0).
K */
Kunsigned int
KSplit(n, ppcList, ppcLeft, ppcRight)
Kunsigned int n;
Kchar **ppcList, **ppcLeft, **ppcRight;
K{
K	register unsigned int i, iLcs, iLcp;
K	register char *pcEnd;
K	auto unsigned int iLcsLen, iLcpLen;
K	auto int cKeep;
K
K	*ppcLeft = (char *)0;
K	*ppcRight = (char *)0;
K	if (n == 1) {
K		return 1 ;
K	}
K
K	iLcp = Prefix(n, ppcList, & iLcpLen);
K	if (iLcp * iLcpLen < 2 + iLcpLen) {
K		iLcp = 0;
K	}
K
K	iLcs = Suffix(n, ppcList, & iLcsLen);
K	if (iLcs * iLcsLen < 2 + iLcsLen) {
K		iLcs = 0;
K	}
K
K	if (iLcp * iLcpLen < iLcs * iLcsLen) {
K		pcEnd = strrchr(ppcList[0], '\000') - iLcsLen;
K		*ppcRight = strsave(pcEnd);
K		for (i = 0; i < iLcs; ++i) {
K			pcEnd = strrchr(ppcList[i], '\000') - iLcsLen;
K			*pcEnd = '\000';
K		}
K		iLcp = Prefix(iLcs, ppcList, & iLcpLen);
K		if (iLcp == iLcs) {
K			pcEnd = ppcList[0] + iLcpLen;
K			cKeep = *pcEnd;
K			*pcEnd = '\000';
K			*ppcLeft = strsave(ppcList[0]);
K			*pcEnd = cKeep;
K			for (i = 0; i < iLcp; ++i) {
K				ppcList[i] += iLcpLen;
K			}
K		}
K		return iLcs;
K	} else if (0 != iLcpLen && 0 != iLcp) {
K		pcEnd = ppcList[0] + iLcpLen;
K		cKeep = *pcEnd;
K		*pcEnd = '\000';
K		*ppcLeft = strsave(ppcList[0]);
K		*pcEnd = cKeep;
K		for (i = 0; i < iLcp; ++i) {
K			ppcList[i] += iLcpLen;
K		}
K		iLcs = Suffix(iLcp, ppcList, & iLcsLen);
K		if (iLcs == iLcp) {
K			pcEnd = strrchr(ppcList[0], '\000') - iLcsLen;
K			*ppcRight = strsave(pcEnd);
K			for (i = 0; i < iLcs; ++i) {
K				pcEnd = strrchr(ppcList[i], '\000') - iLcsLen;
K				*pcEnd = '\000';
K			}
K		}
K		return iLcp;
K	}
K	return 0;
K}
K/* If there are matched curlies around a
K * member of the list we can remove them.
K * uLen may be (a few chars) too big, who cares?
K */
Kvoid
Kmcat(pcAccum, pcElement)
KPREG char *pcAccum, *pcElement;
K{
K	extern int strlen();
K	register char *pcMatch;
K	register unsigned int uLen;
K
K	if ('{' == pcElement[0]) {
K		uLen = strlen(pcElement)-1;
K		pcMatch = FindMatch(pcElement, '{', '}', 0);
K		if (pcMatch == & pcElement[uLen]) {
K			*pcMatch = '\000';
K			strcat(pcAccum, pcElement+1);
K			*pcMatch = '}';
K		} else {
K			strcat(pcAccum, pcElement);
K		}
K	} else {
K		strcat(pcAccum, pcElement);
K	}
K}
K
K/*
K * undo what a {...} does in csh					(ksb)
K * We make passes over the list until we can make no more reductions.
K * I think this works -- that is it does as good a job as I would.
K */
Kunsigned int
KUnCurly(n, ppcWhole)
Kunsigned int n;
Kchar **ppcWhole;
K{
K	register unsigned int m, i;
K	register char **ppcList;
K	auto unsigned int uInside, uLen, uEnd, uSquish;
K	auto char *pcLeft, *pcRight;
K	auto char *pcTemp, *pcSep;
K
K	ppcList = ppcWhole;
K	m = n;
K	while (m > 0) {
K		uInside = Split(m, ppcList, & pcLeft, & pcRight);
K		switch (uInside) {
K		case 0:
K		case 1:
K			/* skip boring files for next pass
K			 */
K			--m;
K			++ppcList;
K			break;
K		default:
K			/* Left "{" List[0] "," List[uInside-1] "}" Right
K			 */
K			n -= m;
K			uSquish = UnCurly(uInside, ppcList);
K			uLen = 2;	/* close curly and "\000" */
K			if ((char *)0 != pcLeft) {
K				uLen += strlen(pcLeft);
K			}
K			for (i = 0; i < uSquish; ++i) {
K				uLen += 1 + strlen(ppcList[i]);
K			}
K			if ((char *)0 != pcRight) {
K				uLen += strlen(pcRight);
K			}
K			pcTemp = malloc(uLen);
K			if ((char *)0 == pcTemp) {
K				fprintf(stderr, acNoMem, progname);
K				exit(1);
K			}
K
K			pcTemp[0] = '\000';
K			if ((char *)0 != pcLeft) {
K				(void) strcat(pcTemp, pcLeft);
K				free(pcLeft);
K			}
K			if (1 == uSquish) {
K				mcat(pcTemp, ppcList[0]);
K			} else {
K				pcSep = "{";
K				for (i = 0; i < uSquish; ++i) {
K					register char *pcMatch;
K
K					strcat(pcTemp, pcSep);
K
K					mcat(pcTemp, ppcList[i]);
K					pcSep = ",";
K				}
K				strcat(pcTemp, "}");
K			}
K			if ((char *)0 != pcRight) {
K				(void) strcat(pcTemp, pcRight);
K				free(pcRight);
K			}
K
K			uEnd = UnCurly(m-uInside, ppcList+uInside);
K			n += 1 + uEnd;
K			ppcList[0] = pcTemp;
K			for (i = 0 ; i < uEnd; /* update below */) {
K				ppcList[++i] = ppcList[uInside++];
K			}
K			ppcList = ppcWhole;
K			m = n;
K			break;
K		}
K	}
K	return n;
K}
K
K/*
K * do the opposite of csh(1) {...}					(ksb)
K * we cannot process files with a comma in them, but as a special
K * case we will remove ",EXT" from the end of a list of files...
K * and process those if it is the only comma in each of the files.
K *  1) output UnCulry of files with no commas
K *  2) output UnCulry of files with `,EXT' (only) on the end
K *  3) output files with random commas in them (bletch)
K *  4) loop until all files have been done
K */
Kint
Kmain(argc, argv)
Kunsigned int argc;
Kchar **argv;
K{
K	register unsigned int i, uReplace, uCommon;
K	register char *pcExt;
K
K	progname = *argv++;
K	--argc;
K
K#if defined(READSTDIN)
K	if (argc == 0) {
K		argc = GetFiles(& argv);
K	}
K#endif
K	while (0 < argc) {
K		for (uCommon = 0; uCommon < argc; ++uCommon) {
K			if ((char *)0 != strrchr(argv[uCommon], ',')) {
K				break;
K			}
K		}
K		if (0 != uCommon) {
K			uReplace = UnCurly(uCommon, argv);
K			argc -= uCommon;
K			for (i = 0; i < uReplace; ++i) {
K				puts(argv[i]);
K			}
K			argv += uCommon;
K		}
K		do {
K			pcExt = (char *)0;
K			for (uCommon = 0; uCommon < argc; ++uCommon) {
K				register char *pcComma;
K				if ((char *)0 == (pcComma = strrchr(argv[uCommon], ','))) {
K					break;
K				}
K				if ((char *)0 == pcExt) {
K					*pcComma ='\000';
K					pcExt = pcComma+1;
K				} else if (0 != strcmp(pcExt, pcComma+1)) {
K					break;
K				} else {
K					*pcComma = '\000';
K				}
K				if ((char *)0 != strrchr(argv[uCommon], ',')) {
K					*pcComma = ',';
K					break;
K				}
K			}
K			if (0 != uCommon) {
K				uReplace = UnCurly(uCommon, argv);
K				argc -= uCommon;
K				for (i = 0; i < uReplace; ++i) {
K					fputs(argv[i], stdout);
K					putchar(',');
K					puts(pcExt);
K				}
K				argv += uCommon;
K			}
K			if ((char *)0 != strrchr(argv[0], ',')) {
K				puts(argv[0]);
K				argc -= 1;
K				argv += 1;
K				uCommon = 1;
K			}
K		} while (0 != uCommon);
K	}
K	exit(0);
K}
SHAR_EOF
#	End of shell archive
exit 0