fix_eoln.c, a tool to change line endings between PC, Mac, and Unix

Alex Cherepanov <[email protected]> Fri, 01 Oct 2004 16:25:25 -0400
Newsgroups gmane.comp.printing.ghostscript.patches
Organization Coscript Software
Message-ID <[email protected]>
This is the latest version of the tool.
Following Igor's request to add the conversion from Unix
to MS-DOS style, the program was generalized to convert
from any style to any style. The name of the program has been
changed accordingly.

The extension of the command line interface made ad-hoc command line 
parser unfeasible. By using getopt_long() the option processing has
been brought up to the standard. For instance, we've got '--' for
free.

getopt_long() is a part of glibc. When getopt_long() is not available
as a part of the libc, a few source files from glibc can be compiled
into fix_eoln executable. Files getopt.c, getopt.h , getopt1.c are not
attached.  Probably, it also needs a makefile, configure
script, copying statement, readme, man page, and an own directory.

Since glibc is distributed under LGPL, the
product that includes sources from the glibc must be distributed under
a LGPL-compatible licence. Probably, it also needs a makefile, configure
script, copying statement, readme, man page, etc.

It the portability to non-glibc platforms can be dropped
fix_eoln.c compiles as:
    gcc fix_eoln.c

The binary file recognition was based on the presence of the control
characters ( c < 32 ). So the PDF recovery did not require --force
option even in the previous version.

The message about skipping binary files is now printed to stderr in
--verbose mode.

_______________________________________________
gs-code-review mailing list
[email protected]
http://www.ghostscript.com/mailman/listinfo/gs-code-review
fix_eoln.c (text/plain, 6.6 KB)
/*
 * Copyright 2004 artofcode LLC
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
/* 
 * Change line endings between UNIX, MS-DOS, and Mac standards in place.
 * The file is modified only when needed.
 * Binary files are ignored unless forced.
 * Binary files are not modified except in the forced mode.
 */
char version[] = "$Id: $";

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef _MSC_VER
#  define lseek   _lseek
#  define fileno  _fileno
#  define stat    _stat
#  define S_ISDIR(s) (s & _S_IFDIR)
#  define __STDC__ 1
#  include "getopt.h"
#else
#  include <getopt.h>
#endif

#define EOLN 256		/* out of the character range */

typedef enum tag_eoln
{ k_cr = 1, k_ln = 2, k_crln = 4 } eoln_t;

static void
error (int code, char *fmt, ...)
{
  va_list v;
  va_start (v, fmt);
  vfprintf (stderr, fmt, v);
  perror ("strerror");
  va_end (v);
  if (code)
    exit (code);
}

static void
usage (void)
{
  printf
    ("Change line endings between UNIX, MS-DOS, and Mac standards in place.\n"
     "The file is modified only when needed.\n"
     "Binary files are ignored unless forced.\n\n"
     "Usage:\tfix_eoln [OPTIONS]... [FILE]...\n"
     "  -D, --dos-in\t\tconvert MS-DOS line terminator '\\r\\n'\n"
     "  -d, --dos-out\t\toutput MS-DOS format, '\\r\\n'\n"
     "  -f, --force\t\tmodify the file even if it looks like binary\n"
     "  -M, --mac-in\t\tconvert Mac line terminator, '\\r'\n"
     "  -m, --mac-out\t\toutput Mac line terminator, '\\r'\n"
     "  -n, --not-fix\t\tlist files but don't change them\n"
     "  -U, --unix-in\t\tconvert UNIX line terminator, '\\n'\n"
     "  -u, --unix-out\toutput UNIX line terminator, '\\n'\n"
     "  -v, --verbose\t\tdisplay more messages\n"
     "      --\t\tforces the end of option scanning\n"
     "      --help\t\tdisplay this help and exit\n"
     "      --version\t\toutput version information and exit\n\n"
     "By default all line engings are recognised and convertrd to UNIX style.\n");
}

static const struct option opt_long[] = {
  {"dos-in", 0, 0, 'D'},
  {"dos-out", 0, 0, 'd'},
  {"force", 0, 0, 'f'},
  {"mac-in", 0, 0, 'M'},
  {"mac-out", 0, 0, 'm'},
  {"not-fix", 0, 0, 'n'},
  {"unix-in", 0, 0, 'U'},
  {"unix-out", 0, 0, 'u'},
  {"verbose", 0, 0, 'v'},
  {"help", 0, 0, '\001'},
  {"version", 0, 0, '\002'},
  {0, 0, 0, 0}
};
static const char opt_short[] = "+DdfMmnUuv";

static int
is_binary (char *p, int len)
{
  int i, rc = 0, cnt[256];

  memset (cnt, 0, sizeof (cnt));
  for (i = 0; i < len; i++)
    cnt[p[i] & 0xFF] = 1;

  cnt['\r'] = cnt['\n'] = cnt['\t'] = cnt['\f'] = 0;
  for (i = 0; i < 32; i++)
    rc |= cnt[i];
  return rc;
}


static int
need_fix (char *buf, int len, int eoln_in, eoln_t eoln_out)
{
  int j;
  for (j = 0; j < len; j++)
    {
      char c = buf[j];
      if (c == '\r' && buf[j + 1] == '\n' && (eoln_in & k_crln))
	{
	  j++;
	  if (eoln_out != k_crln)
	    return 1;
	}
      if (c == '\r' && (eoln_in & k_cr))
	{
	  if (eoln_out != k_cr)
	    return 1;
	}
      if (c == '\n' && (eoln_in & k_ln))
	{
	  if (eoln_out != k_ln)
	    return 1;
	}
    }
  return 0;
}

/* Write the character or correct EOLN sequence */
static int
putc_or_eoln (int c, FILE * f, eoln_t eoln_out)
{
  if (c == EOLN)
    {
      switch (eoln_out)
	{
	case k_crln:
	  if (putc ('\r', f) == EOF)
	    return EOF;
	  if (putc ('\n', f) == EOF)
	    return EOF;
	  return 0;
	case k_cr:
	  return putc ('\r', f);
	case k_ln:
	default:
	  return putc ('\n', f);
	}
    }
  else
    return putc (c, f);
}

int
main (int argc, char **argv)
{
  int opt, opt_index, i;
  int force = 0, not_fix = 0, verbose = 0, eoln_in = 0;	/* options */
  eoln_t eoln_out = k_ln;	/* options */

  while ((opt =
	  getopt_long (argc, argv, opt_short, opt_long, &opt_index)) != -1)
    {
      switch (opt)
	{
	case 0:
	case '?':
	  break;
	case 1:
	  usage ();
	  exit (0);
	  break;
	case 2:
	  printf ("%s\n", version);
	  exit (0);
	  break;
	case 'D':
	  eoln_in += k_crln;
	  break;
	case 'd':
	  eoln_out = k_crln;
	  break;
	case 'f':
	  force = 1;
	  break;
	case 'M':
	  eoln_in += k_cr;
	  break;
	case 'm':
	  eoln_out = k_cr;
	  break;
	case 'n':
	  not_fix = 1;
	  break;
	case 'U':
	  eoln_in += k_ln;
	  break;
	case 'u':
	  eoln_out = k_ln;
	  break;
	case 'v':
	  verbose = 1;
	  break;
	default:
	  error (3, "Unexpected character code from getopt_long() 0x%x\n",
		 opt);
	}
    }
  if (eoln_in == 0)
    eoln_in = k_crln | k_cr | k_ln;

  for (i = optind; i < argc; i++)
    {
      unsigned long len;
      char *buf;
      struct stat st;
      FILE *f;

      if (stat (argv[i], &st) == EOF)
	error (1, "Cannot get status on %s\n", argv[i]);
      if (S_ISDIR (st.st_mode))
	continue;
      len = st.st_size;

      f = fopen (argv[i], "rb");
      if (f == 0)
	error (2, "Cannot open %s for reading\n", argv[i]);

      buf = malloc (len + 1);
      if (buf == 0)
	{
	  fclose (f);
	  error (4, "Cannot allocate %d bytes\n", len + 1);
	}
      buf[len] = 0;

      fseek (f, 0, SEEK_SET);
      if (fread (buf, 1, len, f) != len)
	{
	  free (buf);
	  fclose (f);
	  error (5, "Error reading %s\n", argv[i]);
	}
      fclose (f);
      if (need_fix (buf, len, eoln_in, eoln_out))
	{
	  unsigned long j;
	  if (!force && is_binary (buf, len))
	    {
	      if (verbose)
		fprintf (stderr, "Skipping binary file %s\n", argv[i]);
	    }
	  else
	    {
	      printf ("%s\n", argv[i]);
	      if (!not_fix)
		{
		  f = fopen (argv[i], "wb");
		  if (!f)
		    {
		      free (buf);
		      error (6, "Cannot open %s for writing\n", argv[i]);
		    }
		  for (j = 0; j < len; j++)
		    {
		      int c = buf[j];
		      if (c == '\r' && buf[j + 1] == '\n'
			  && (eoln_in & k_crln))
			{
			  j++;
			  c = EOLN;
			}
		      else if (c == '\r' && (eoln_in & k_cr))
			c = EOLN;
		      else if (c == '\n' && (eoln_in & k_ln))
			c = EOLN;
		      if (putc_or_eoln (c, f, eoln_out) == EOF)
			{
			  free (buf);
			  error (7, "Error writing %s\n", argv[i]);
			}
		    }
		  fclose (f);
		}
	    }
	}
      free (buf);
    }
  return 0;
}