/*  _cwild.c - (reasonable, i.e. **IX style) wildcard expansion and
    passing of long commandlines for MSC
    Copyright (C) 1990 by Thorsten Ohl, td12@ddagsi3.bitnet

    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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Header: e:/gnu/lib/RCS/_cwild.c 1.0 90/09/11 01:55:11 tho Exp $
 */


/* One of the major shortcomings of MS-DOS is the inability to pass long
   commandlines.  Commandlines are restricted to 126 characters in length
   and may not contain newlines, etc.

   Both of these restrictions make it impossible to put reasonably sized
   `awk', `sed', `perl', etc. scripts into shell scripts.  Also it's
   impossible to put reasonably sized shell script fragments into
   makefiles.

   One way to overcome *some* of these problems is to let the user
   commands perform their own globbing.  The second (more satisfactory)
   way is to invent a new scheme for argument passing.

   One approach that presents itself, is to pass the arguments via the
   environment.  This is the approach adopted here.  If this code is
   compiled with `-DWANT_LONG_COMMANDLINES' (which is implied by
   `-DSMART_SHELL_ONLY' and `-DSMART_SHELL'), the startup code looks for
   "_argc" in the environment.  If it is found, it is used as a argument
   count and the commandline is taken from "_argv0", "_argv1", ...  The
   MS-DOS commandline is *ignored* in this case!  This is of course only
   useful if your Shell or Make supports this feature.  (GNU make will do
   in the next version.)

   The other part of the code uses the GNU globbing library to perform
   globbing of the MS-DOS commandline in a **IX compatible way.

   This code has been developped to be used with the startup code of
   Microsoft C.  Compile with one of SMART_SHELL_ONLY, SMART_SHELL, or
   DUMB_SHELL_ONLY.  Link with the `setargv.obj' that came with the
   compiler to ensure that _cwild () is called during startup.

   IMPORTANT:  Since we want this appraoch to become compiler independent,
   feedback in form of patches for other compilers (Turbo C, Zortech,
   etc.) is *greatly* appreciated.  */


/* NOTE:  Even though this approach is almost evident, I do *not* put it
   in the public domain.  I CLAIM A COPYRIGHT BOTH ON THIS CODE AND THE
   UNDERLYING IDEAS.  But you are welcome to redistribute, use and
   improve it under the terms of the GNU GENERAL PUBLIC LICENSE.

   This seems appropriate in the days of aggressive copyright lawsuits
   to prevent someone from preventing YOU to use it.  */


/* Configuration Section:  */

/* Define this if you are exclusively using Bash, GNU make,
   or other winning utilities which take the load of expanding
   the commandline from your program and know how to pass
   *long* commandlines.  */

#ifdef SMART_SHELL_ONLY

#ifndef WANT_LONG_COMMANDLINES
#define WANT_LONG_COMMANDLINES
#endif

#ifdef WANT_GLOBBING
#undef WANT_GLOBBING
#endif

#endif /* SMART_SHELL_ONLY */


/* Define this if you are always use loosing Shells and Makes, that don't
   know how to pass long commandlines.   And if your program has to expand
   the commandline itself.  */

#ifdef DUMB_SHELL_ONLY

#ifdef WANT_LONG_COMMANDLINES
#undef WANT_LONG_COMMANDLINES
#endif

#ifndef WANT_GLOBBING
#define WANT_GLOBBING
#endif

#endif /* not DUMB_SHELL_ONLY */


#if !defined(SMART_SHELL) && !defined(SMART_SHELL_ONLY) \
  && !defined(DUMB_SHELL_ONLY)
#define SMART_SHELL
#endif

/* Define this if your program can expect long commandlines, but should
   also be able to do some globbing. */

#ifdef SMART_SHELL

#ifndef WANT_LONG_COMMANDLINES
#define WANT_LONG_COMMANDLINES
#endif

#ifndef WANT_GLOBBING
#define WANT_GLOBBING
#endif

#endif /* SMART_SHELL */


/* This code relies on the standard GNU globbing library, which does
   all the hard work.  */

/* This wildcard expansion module is tailored for use with the Microsoft
   C startup code and uses their conventions.  It will probably not work
   with the libraries of other compilers.  */

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


#ifdef WANT_LONG_COMMANDLINES
static int _cenv (void);
#define LONG_ARGS_PRESENT	(getenv ("_argc") != NULL)
#else
#define _cenv()			1
#define LONG_ARGS_PRESENT	0
#endif


#ifdef WANT_GLOBBING
#include <gnulib.h>
static int compare (char **s1, char **s2);
static int _cglob (void);
#else
#define _cglob()		1
#endif

int _cwild (void);



/* The external argument vector:  */

extern int __argc;
extern char ** __argv;


/* This is the main entry point, decide whether we have a smart shell
   or have to do the globbing ourselves.  */

int
_cwild (void)
{
  return LONG_ARGS_PRESENT ? _cenv () : _cglob ();
}


#ifdef WANT_LONG_COMMANDLINES

/* We have been called by a smart shell, get arguments from the
   environment entries "_argc", "_argv0", "_argv1", ...  */

int
_cenv (void)
{
  int ind = 0;
  char **argv;

  /* We know that "_argc" is there! */

  __argc = atoi (getenv ("_argc"));
  if (__argc == 0)
    return -1;

  argv = (char **) malloc ((__argc + 1) * sizeof (char *));
  if (argv == NULL)
    return -1;

  while (ind < __argc)
    {
      char entry[10];
      char *p;

      sprintf (entry, "_argv%d", ind);
      p = getenv (entry);
      if (p != 0)
	argv[ind] = strcpy (malloc (strlen (p) + 1), p);
      else
	argv[ind] = strcpy (malloc (1), "");

      ind++;
    }

  /* Terminate  */
  argv[__argc] = NULL;

  /* Install  */
  __argv = argv;

  return 0;
}

#endif /* WANT_LONG_COMMANDLINES */


#ifdef WANT_GLOBBING

/* During startup, perform filename globbing of __ARGV.  By Microsoft
   convention, the first letter of each member of __ARGV marks wether
   globbing is to be performed for this specific name.  If it is '\"',
   the string is left alone.  One has to be careful, not to include
   this character in the result.  */

int
_cglob (void)
{
  int argc = __argc;
  char **argv = __argv;
  int optind;


  /* Vector of vectors of globbed filenames, terminated by NULL.
     If GLOB_ARGV[N] == -1, or if *(GLOB_ARGV[N]) == NULL, no
     globbing has been performed and the original __ARGV[N] will
     be used. (Strictly speaking, GLOB_ARGVV[0] is never used,
     but we don't care.) */

  char ***glob_argvv = (char ***) malloc (argc * sizeof (char **));

  if (glob_argvv == NULL)
    return -1;

  __argc = 2;			/* program name and terminating NULL */


  /* Pass 1: Find out how much storage we need and allocate it.  */

  for (optind = 1; optind < argc; optind++)
    {
      /* there's at least one (the original argv[optind]).  */

      __argc++;

      if (*(argv[optind])++ == '\"')
	{
	  /* Don't glob ARGV[OPTIND], but strip the leading quote marker  */

	  glob_argvv[optind] = (char **) -1;
	}
      else
	{
	  char **ptr = glob_argvv[optind]  = glob_filename (argv[optind]);
	  size_t cnt = 0;

	  /* we will ignore errors from glob_filename () and pass the
	     unexpanded argument  */

	  if (ptr != (char **) -1 && *ptr++ != NULL)
	    while (*ptr++ != NULL)
	      cnt++;

	  /* Sort the globbed filenames */

	  if (cnt > 0)
	    qsort (glob_argvv[optind], cnt + 1, sizeof (char *), compare);

	  __argc += cnt;
	}
    }

  __argv = (char **) malloc ((__argc + 1) * sizeof (char *));

  if (__argv == NULL)
    {
      __argv = argv;		/* failed */
      return -1;
    }


  /* Pass 2: Build the new commandline.  */

  __argc = 1;
  __argv[0] = argv[0];

  if (*(__argv[0])++ != '\"')		  /* Reformat the program name.  */
    msdos_format_filename (__argv[0]);

  for (optind = 1; optind < argc; optind++)
    {
      char **ptr = glob_argvv[optind];

      /* Did we perform globbing?  */

      if (ptr == (char **) -1 || *ptr == NULL)
	__argv[__argc++] = argv[optind];
      else
	while (*ptr)
	  __argv[__argc++] = *ptr++;
    }


  __argv[__argc] = NULL;		/* terminate */
  free (glob_argvv);			/* cleanup */

  return 0;
}


/* This is for passing strcmp () to qsort ().  */
int
compare (char **s1, char **s2)
{
  return strcmp (*s1, *s2);
}

#endif /* WANT_GLOBBING */


/* Filenames returned by MS-DOS system calls are formatted very ugly:
   all uppercase and backslashes.  Perform some cosmetics.  */

char *
msdos_format_filename (char *name)
{
  char *p = name;
  while (*p = (*p == '\\') ? '/' : tolower (*p))
    p++;
  return name;
}

#ifdef TEST

void
main (int argc, char **argv)
{
  if (*++argv)
    {
      printf ("%s", *argv);
      while (*++argv)
	printf (" %s", *argv);
    }
  exit (0);
}

#endif /* TEST */

/* 
 * Local Variables:
 * mode:C
 * ChangeLog:ChangeLog
 * compile-command:cl -DSMART_SHELL -DTEST _cwild.c glob msd_dir d:\ms\lib\setgarv -link /noe
 * End:
 */
