/*
# 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