1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1992-2011 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                 Glenn Fowler <gsf@research.att.com>                  *
18*                  David Korn <dgk@research.att.com>                   *
19*                                                                      *
20***********************************************************************/
21#pragma prototyped
22/*
23 * David Korn
24 * AT&T Bell Laboratories
25 *
26 * paste [-s] [-d delim] [file] ...
27 *
28 * paste lines from files together
29 */
30
31static const char usage[] =
32"[-?\n@(#)$Id: paste (AT&T Research) 2010-06-12 $\n]"
33USAGE_LICENSE
34"[+NAME?paste - merge lines of files]"
35"[+DESCRIPTION?\bpaste\b concatenates the corresponding lines of a "
36	"given input file and writes the resulting lines to standard "
37	"output.  By default \bpaste\b replaces the newline character of "
38	"every line other than the last input file with the TAB character.]"
39"[+?Unless the \b-s\b option is specified, if an end-of-file is encountered "
40	"on one or more input files, but not all input files, \bpaste\b "
41	"behaves as if empty lines were read from the file(s) on which "
42	"end-of-file was detected.]"
43"[+?Unless the \b-s\b option is specified, \bpaste\b is limited by "
44	"the underlying operating system on how many \afile\a operands "
45	"can be specified.]"
46"[+?If no \afile\a operands are given or if the \afile\a is \b-\b, \bpaste\b "
47	"reads from standard input. The start of the file is defined as the "
48	"current offset.]"
49
50"[s:serial?Paste the lines of one file at a time rather than one line "
51	"from each file.  In this case if the \b-d\b option is "
52	"specified the delimiter will be reset to the first in the "
53	"list at the beginning of each file.]"
54"[d:delimiters]:[list?\alist\a specifies a list of delimiters.  These "
55	"delimiters are used circularly instead of TAB to replace "
56	"the newline character of the input lines. Unless the \b-s\b "
57	"option is specified, the delimiter will be reset to the first "
58	"element of \alist\a each time a line is processed from each file.  "
59	"The delimiter characters corresponding to \alist\a will be found "
60	"by treating \alist\a as an ANSI-C string, except that the \b\\0\b "
61	"sequence will insert the empty string instead of the null character.]"
62"\n"
63"\n[file ...]\n"
64"\n"
65"[+EXIT STATUS?]{"
66	"[+0?All files processed successfully.]"
67	"[+>0?An error occurred.]"
68"}"
69"[+SEE ALSO?\bcut\b(1), \bcat\b(1), \bjoin\b(1)]"
70;
71
72#include <cmd.h>
73
74typedef struct Delim_s
75{
76	const char*	chr;
77	size_t		len;
78} Delim_t;
79
80/*
81 * paste the lines of the <nstreams> defined in <streams> and put results
82 * to <out>
83 */
84
85static int paste(int nstream,Sfio_t* streams[],Sfio_t *out, register const char *delim, int dsiz, int dlen, Delim_t* mp)
86{
87	register const char *cp;
88	register int d, n, i, z, more=1;
89	register Sfio_t *fp;
90	do
91	{
92		d = (dlen>0?0:-1);
93		for(n=more-1,more=0; n < nstream;)
94		{
95			if(fp=streams[n])
96			{
97				if(cp = sfgetr(fp,'\n',0))
98				{
99					if(n==0)
100						more = 1;
101					else if(!more) /* first stream with output */
102					{
103						if(dsiz == 1)
104							sfnputc(out, *delim, n);
105						else if(dlen>0)
106						{
107							for(d=n; d>dlen; d-=dlen)
108								sfwrite(out,delim,dsiz);
109							if(d)
110							{
111								if(mp)
112									for (i = z = 0; i < d; i++)
113										z += mp[i].len;
114								else
115									z = d;
116								sfwrite(out,delim,z);
117							}
118						}
119						more = n+1;
120					}
121					if(sfwrite(out,cp,sfvalue(fp)-((n+1)<nstream)) < 0)
122						return(-1);
123				}
124				else
125					streams[n] = 0;
126			}
127			if(++n<nstream && more && d>=0)
128			{
129				register int c;
130				if(d >= dlen)
131					d = 0;
132				if(mp)
133					sfwrite(out,mp[d].chr,mp[d].len);
134				else if(c=delim[d])
135					sfputc(out,c);
136				d++;
137			}
138			else if(n==nstream && !streams[n-1] && more)
139				sfputc(out,'\n');
140		}
141	} while(more);
142	return(0);
143}
144
145/*
146 * Handles paste -s, for file <in> to file <out> using delimiters <delim>
147 */
148static int spaste(Sfio_t *in,register Sfio_t* out,register const char *delim,int dsiz,int dlen,Delim_t* mp)
149{
150	register const char *cp;
151	register int d=0;
152	if((cp = sfgetr(in,'\n',0)) && sfwrite(out,cp,sfvalue(in)-1) < 0)
153		return(-1);
154	while(cp=sfgetr(in, '\n',0))
155	{
156		if(dlen)
157		{
158			register int c;
159			if(d >= dlen)
160				d = 0;
161			if(mp)
162				sfwrite(out,mp[d].chr,mp[d].len);
163			else if(c=delim[d])
164				sfputc(out,c);
165			d++;
166		}
167		if(sfwrite(out,cp,sfvalue(in)-1) < 0)
168			return(-1);
169	}
170	sfputc(out,'\n');
171	return(0);
172}
173
174int
175b_paste(int argc,register char *argv[], void* context)
176{
177	register int		n, sflag=0;
178	register Sfio_t		*fp, **streams;
179	register char 		*cp, *delim;
180	char			*ep;
181	Delim_t			*mp;
182	int			dlen, dsiz;
183	char			defdelim[2];
184
185	cmdinit(argc, argv, context, ERROR_CATALOG, 0);
186	delim = 0;
187	for (;;)
188	{
189		switch (optget(argv, usage))
190		{
191		case 'd':
192			delim = opt_info.arg;
193			continue;
194		case 's':
195			sflag++;
196			continue;
197		case ':':
198			error(2, "%s", opt_info.arg);
199			break;
200		case '?':
201			error(ERROR_usage(2), "%s", opt_info.arg);
202			break;
203		}
204		break;
205	}
206	argv += opt_info.index;
207	if(error_info.errors)
208		error(ERROR_usage(2),"%s", optusage(NiL));
209	if(!delim || !*delim)
210	{
211		delim = defdelim;
212		delim[0] = '\t';
213		delim[1] = 0;
214	}
215	if (!(delim = strdup(delim)))
216		error(ERROR_system(1), "out of space");
217	dlen = dsiz = stresc(delim);
218	mp = 0;
219	if (mbwide())
220	{
221		cp = delim;
222		ep = delim + dlen;
223		dlen = 0;
224		while (cp < ep)
225		{
226			mbchar(cp);
227			dlen++;
228		}
229		if(dlen < dsiz)
230		{
231			if (!(mp = newof(0, Delim_t, dlen, 0)))
232			{
233				free(delim);
234				error(ERROR_system(1), "out of space");
235			}
236			cp = delim;
237			dlen = 0;
238			while (cp < ep)
239			{
240				mp[dlen].chr = cp;
241				mbchar(cp);
242				mp[dlen].len = cp - mp[dlen].chr;
243				dlen++;
244			}
245		}
246	}
247	if(cp = *argv)
248	{
249		n = argc - opt_info.index;
250		argv++;
251	}
252	else
253		n = 1;
254	if(!sflag)
255	{
256		if (!(streams = (Sfio_t**)stakalloc(n*sizeof(Sfio_t*))))
257			error(ERROR_exit(1), "out of space");
258		n = 0;
259	}
260	do
261	{
262		if(!cp || streq(cp,"-"))
263			fp = sfstdin;
264		else if(!(fp = sfopen(NiL,cp,"r")))
265			error(ERROR_system(0),"%s: cannot open",cp);
266		if(fp && sflag)
267		{
268			if(spaste(fp,sfstdout,delim,dsiz,dlen,mp) < 0)
269				error(ERROR_system(0),"write failed");
270			if(fp!=sfstdin)
271				sfclose(fp);
272		}
273		else if(!sflag)
274			streams[n++] = fp;
275	} while(cp= *argv++);
276	if(!sflag)
277	{
278		if(error_info.errors==0 && paste(n,streams,sfstdout,delim,dsiz,dlen,mp) < 0)
279			error(ERROR_system(0),"write failed");
280		while(--n>=0)
281			if((fp=streams[n]) && fp!=sfstdin)
282				sfclose(fp);
283	}
284	if (mp)
285		free(mp);
286	free(delim);
287	return(error_info.errors);
288}
289