/*
# getline.c
# Written by D'Arcy J.M. Cain
# darcy@druid.net
# Copyright 1991, 1992

# This code may be used on any computer system for any purpose by anyone

NAME
	getline, xgetline, wgetline

SYNOPSIS
	char *getline(FILE *fp, char *buf);
	char *xgetline(FILE *fp, char *buf, size_t *linenum);
	char *sgetline(FILE *fp, char *buf);

	xcat [file ...] # shell function

DESCRIPTION
	Reads a line from the stream given by fp (or from the window given by
	win in wgetline) and returns a pointer to the string.  There is no
	length restriction on the returned string.  Space is dynamically
	allocated for the string as needed.  The pointer given is realloced
	so the calling function should not expect the pointer to be valid
	after the call.  It should reset the pointer to the return value
	of this function.  At end of file the space will be freed.  The way
	to use this function would be something like this:

		char *buf = NULL;
		FILE *fp;
		... open file ...
		while ((buf = getline(fp, buf)) != NULL)
			... do stuff with buf ...

	Because the xgetline may read an unknown number of lines, the linenum
	variable allows the calling process to track the real line number.  If
	it is non-NULL then it is taken as a pointer to a variable that gets
	incremented each time the function reads another raw line in.

	In the xgetline version anything from '#' till the end of line is
	ignored and A  trailing '\' character is treated as a continuation
	character.  After this processing the resulting line is ignored if
	it is empty.  The '#' and '\' characters can be included by preceding
	them with a '\'.  Note that the leading backslash is left in the input
	string and must be dealt with by the caller.  This can be somewhat of a
	problem at the end of a line but in general should be workable.

	If the caller wants to keep the memory it should call with the buf
	argument set to NULL every time.

	If the first argument is NULL then the function returns its second arg.

	Defining XCAT causes a main function to be included allowing the
	xgetline function to be available in shell programs.

	I prototype getline and xgetline in stdio.h since their use always
	involves stdio routines.  See further discussion below.

	Note there used to be a curses version but it was so seldom used
	that I removed it for simplicity.  You generally don't want to
	have unbounded input in a curses window.

	The sgetline version is for a special case in one of my packages
	and you shouldn't worry about it.

RETURNS
	A pointer to the string without the terminating newline is returned
	if successful or NULL if there was an error or end of file.  Use
	feof(3) and ferror(3) to find out if it was a file error, memory
	allocation problem or EOF condition.

AUTHOR
	D'Arcy J.M. Cain (darcy@druid.net)

*/

#include	<ctype.h>
#include	<errno.h>
#include	<stdlib.h>
#include	<stdio.h>

#ifndef		CTRL
#define		CTRL(x)		((x) & 0x1f)
#endif

/* I originally was going to use 80 here as the most common case but */
/* decided that a few extra bytes to save a malloc from time to time */
/* would be a better choice.  Comments welcome.  */
#define		CHUNK	128

/* XCAT implies XGETLINE */
#ifdef	XCAT
#ifndef	XGETLINE_VERSION
#define	XGETLINE_VERSION
#endif
#endif

#ifdef XGETLINE_VERSION
char	*xgetline(FILE *fp, char *buf, size_t *linenum);
void	xgetline_cchar(char c);
char comment_char = '#';
#else
char	*getline(FILE *fp, char *buf);
#endif

#ifdef	STREAM_VERSION
#	include	"stream.h"
#	define		getline		sgetline
#	define		INPUT_STREAM		int
#	define		mygetc(fp)	stream_getc(fp)
#else
#	define		INPUT_STREAM		FILE *
#	ifdef	XGETLINE_VERSION
#		define	getline		xgetline
		int		xskipwhitespace = 0;	/* skips leading space if true */

#	endif	/* XGETLINE_VERSION */

#	ifdef	__MSDOS__
#		define	mygetc(fp)	getc(fp)
#	else	/* this let's it read DOS files */
		static int
		mygetc(INPUT_STREAM fp)
		{
			int			c;
			static int	last = -1;
		
			if (last != -1)
			{
				c = last;
				last = -1;
				return c;
			}
		
			if ((c = getc(fp)) == '\r')
			{
				if ((c = getc(fp)) != '\n')
					last = c;
		
				return('\n');
			}

			return(c);
		}
#	endif	/* __MSDOS__ */
#endif	/* STREAM_VERSION */

#ifdef	XGETLINE_VERSION
void
xgetline_cchar(char c)
{
	comment_char = c;
}

char *
getline(INPUT_STREAM fp, char *buf, size_t *linenum)
#else
char *
getline(INPUT_STREAM fp, char *buf)
#endif
{
	size_t	sz = CHUNK;		/* this keeps track of the current size of buffer */
	size_t	i = 0;			/* index into string tracking current position */
	char	*ptr;			/* since we may set buf to NULL before returning */
	int		c;				/* to store getc return */
#ifdef	XGETLINE_VERSION
	int		in_comment = 0;	/* if we are in a comment */
	int		in_quote = 0;	/* if we are in quote and holds quote character */
#endif

	/* no real good idea for handling this */
	if (!fp)
		return(buf);

	/* start out with buf set to CHUNK + 2 bytes */
	if ((buf = realloc(buf, CHUNK + 2)) == NULL)
		return(NULL);

	/* get characters from stream until EOF */
#ifdef	XGETLINE_VERSION
	while ((c = mygetc(fp)) != EOF)
#else
	while ((c = mygetc(fp)) != EOF && c != '\n')
#endif
	{
#ifdef	XGETLINE_VERSION
		if (xskipwhitespace && !i && isspace(c) && c != '\n')
			continue;

		buf[i] = c;

		if (c == '\n')
		{
			in_comment = in_quote = 0;		/* nl ends comment and quote */

			if (linenum)
				(*linenum)++;					/* keep track of current line */

			/* lose trailing spaces */
			for (i++; i && isspace((int) buf[i - 1]); i--)
				buf[i - 1] = 0;

			if (i)
				break;
		}
		else if (in_comment)
		{
			/* continuation still ends comment - alternative would be silly */
			if (c == '\\' && (c = mygetc(fp)) == '\n')
			{
				in_comment = 0;

				if (linenum)
					(*linenum)++;

				while (isspace(c = mygetc(fp)))
					;

				ungetc(c, fp);
			}
		}
		else if (in_quote)
		{
			i++;

			if (c == in_quote)
				in_quote = 0;
		}
		else if (c == '\'' || c == '"')
		{
			in_quote = c;
			i++;
		}
		else if (c == comment_char)
			in_comment = 1;
		else if (c == '\\')
		{
			if ((c = mygetc(fp)) != '\n' && c != EOF)
			{
				buf[i++] = '\\';
				buf[i++] = c;
			}
			else
			{
				while (isspace(c = mygetc(fp)))
					;

				ungetc(c, fp);
				buf[i++] = ' ';

				if (linenum)
					(*linenum)++;
			}
		}
		else
			i++;

#else
		/* the following needed in case we are in cbreak or raw mode */
		if (c != '\b')
			buf[i++] = c;
		else if (i)
			i--;
#endif

		/* check for buffer overflow */
		if (i >= sz)
			if ((buf = realloc(buf, (sz += CHUNK) + 2)) == NULL)
				return(NULL);
	}

	/* is there anything to return? */
	if (c == EOF && !i)
	{
		free(buf);
		return(NULL);
	}

	buf[i++] = 0;	/* yes I want the ++ */

	/* test for error but don't bother explaining if it fails */
	if ((ptr = realloc(buf, i)) == NULL)
		ptr = buf;

	return(ptr);
}

#ifdef	TEST_MODULE
int		main(void)
{
	char	*p;

	while ((p = getline(stdin, 0)) != NULL)
		printf("%s\n", p);

	return(0);
}
#endif

#ifdef	XCAT
#include	<string.h>

static void
xcat(FILE *fp)
{
	char	*p = NULL;

	while ((p = xgetline(fp, p, 0)) != NULL)
		printf("%s\n", p);
}

int
main(int argc, char **argv)
{
	FILE	*fp;
	int		k;

	if (argc < 2)
		xcat(stdin);
	else for (k = 1; k < argc; k++)
	{
		if ((fp = fopen(argv[k], "r")) == NULL)
			fprintf(stderr, "xcat: Can't open file %s - %s\n",
								argv[k], strerror(errno));
		else
		{
			xcat(fp);
			fclose(fp);
		}
	}

	return(0);
}
#endif	/* XCAT */



syntax highlighted by Code2HTML, v. 0.9.1