/*

 DIRTREE: Recursively search a directory tree displaying all directories
          with indentation for its level.

 DIRTREE returns EXIT_SUCCESS status code if successful, or EXIT_FAILURE
 if any error or warning is encounterd or for invalid argument usage.

 Written by: Jason Mathews <mathews@mitre.org>

 ---------------------------------------------------------------------------

Copyright (C) 1994  Jason Mathews

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

 ---------------------------------------------------------------------------

History:
  V1.0  15-Sep-94  Original version.
  V1.1  06-Feb-95  Added -w argument to specify column width.

*/

#include <stdio.h>
#include <stdlib.h>	/* for atoi prototype */
#include <string.h>
#include <ctype.h>
#include "dirent.h"

#ifdef UNIX
#  define ALT_SW
#  define STAT lstat /* don't expand symbolic links */
#else /* ?MSDOS\VMS */
#  define ALT_SW || *argv[i]=='/'
#  define STAT stat
#endif

#ifdef VMS
#  define EXIT_SUCCESS 1
#  define EXIT exit
#  define CHDIR(s) vms_chdir(s)
#else /* ?MSDOS/UNIX */
#  define EXIT_SUCCESS 0
#  define EXIT return
#  define CHDIR(s) chdir(s)
#endif

#ifndef EXIT_FAILURE
#  define EXIT_FAILURE !EXIT_SUCCESS
#endif

/* define opendir directory entry */

#if defined(MSDOS) || defined(VMS)
#  define OP_DIR ""
#else /* ?unix */
#  define OP_DIR "."
#endif /* ?MSDOS|VMS */

typedef char Bool;

/* Global variables */

char copyright[]  = "Directory Tree, Copyright (C) 1994 Jason Mathews\n\n";
char cwd[MAXPATH] = "";
int width = 4;

/* Function prototypes */
void    FindDirs OF((char *dir));

#ifdef MSDOS
/* No valid MS-DOS directories start with a period (.),
 * except for .. or ., so anything with a period prefix
 * must be special.
 */
#define SpecialDir(f) (*(f) == '.')

/* parse path and extract drive and path if specified */
int parse_path (char *path, int* drive, char** dir)
{
    /* store drive and parse only if address specified */
    if (path[1] == ':')
    {
	if (isupper(*path)) *drive = *path - 'A';
	else if (islower(*path)) *drive = *path - 'a';
	else return -1;
#ifndef __TURBOC__
	/* Turbo-C compilers use Drive A=0, others use Drive A=1 */
	++(*drive);  /* convert (A=0,B=1,etc.) to (A=1,B=2,etc.) */
#endif
	path += 2;
    }

    if (*path != 0)
    {
	int len = strlen(path);
	/* strip ending separator (if present), which DOS doesn't like. */
	if (len-- > 1 && path[len]=='\\') path[len] = '\0';
	*dir = path;
    }
    return 0;
}

#else /* ?UNIX/VMS */

#ifdef VMS

int vms_chdir( char* dir )
{
  /* kill version number on directories */
  char* s = strchr(dir, ';');
  if (s) *s = '\0';
  return chdir(dir);
}

#else /* ?UNIX */

int SpecialDir OF((const char *path));

/* Function: SpecialDir
 *
 * Purpose:  Test for special directories
 *
 * Returns: 1 if path = "." or ".."
 *          0 otherwise.
 */
int SpecialDir( path )
  const char *path;
{
  if (*path != '.') return 0;
  if (*(++path) == '.') path++;
  return (*path=='/' || *path=='\0');
}

#endif /* ?VMS */

#endif /* ?MSDOS */

void FindDirs(dir)
   char *dir;
{
  DIR* dirp;     /* directory stream */
  struct dirent* dp; /* directory entry  */
  static int level = 0;

  if (dir != NULL && CHDIR(dir)) return;

  if ((dirp = opendir(OP_DIR)) == NULL) {
    if (dir != NULL) chdir(DIR_PARENT);  /* go up a level */
    return;
  }

  if (!getcwd(cwd, MAXPATH)) *cwd = '\0';

  if (level++==0) printf("%s\n", cwd); /* top level */

  while ((dp = readdir(dirp)) != NULL)
    {
      struct stat statbuf;
      if (STAT(dp->d_name, &statbuf)==0 && S_ISDIR(statbuf.st_mode))
	{
	  int i;
#ifndef VMS
	  /*  Ignore directory entries starting with '.'
	   *  which includes the current and parent directories.
	   */
	  if (!SpecialDir(dp->d_name))
#endif
	    {
	      if (level == 1) printf("+");
	      else printf("|%*s+", (level-1)*width-1, "");
	      for (i=0; i < width-1; i++) printf("-");
	      printf("%s\n", dp->d_name);
	      FindDirs(dp->d_name);   /* recursively parse subdirectories */
	    }
	}
    } /* while */
  closedir(dirp);

  if (dir != NULL) chdir(DIR_PARENT);  /* go up a level */
  level--;
}

int main( argc, argv )
  int argc; char **argv;
{
  static char homedir[MAXPATH] = "";	/* current working directory */
  char *path = 0;			/* target path */
#ifdef MSDOS
  int disk = 0, destDisk = 0;
  unsigned ndrives;
#endif

  if (argc > 1)
    {
      Bool err = 0;
      int i, status = EXIT_FAILURE;
#ifdef MSDOS
      GETDRIVE(disk);  destDisk = disk;
#endif
      for (i=1; i < argc; i++)
	{
	  if ( *argv[i]=='-' ALT_SW )
	    switch (ToLower(argv[i][1]))
	      {
	      case '?':
	      case 'h':
		printf("%s", copyright);
		status = EXIT_SUCCESS;
		goto usage;
		
	      case 'w':
		width = atoi(argv[i]+2);
		break;

	      default:
		err = 1;
	      }
#ifdef MSDOS
	  else err = parse_path(argv[i], &destDisk, &path);
#else /* UNIX || VMS */
	  else path = argv[i];
#endif /* ?MSDOS */

	  if (err != 0)
	    {
	      fprintf(stderr, "%sError: invalid option (%s)\n\n",
		      copyright, argv[i]);
usage:
#ifdef MSDOS
      printf("usage: DIRTREE [-wn] [path]\n"
		"  -wn       Width of each column.\n"
		"  path      Directory to search.\n");
#else /* ?UNIX/VMS */
	printf("Usage: dirtree [-w<width>] [directory]\n");
	printf("  -wn       Set width of each column to n characters.\n\n");
#  ifdef VMS
	printf("Example: dirtree STAFF:[MATHEWS.SRC]\n");
#  else
	printf("Example: dirtree -w8 /usr/mathews/src\n");
#  endif
#endif /* ?MSDOS */
	      EXIT(status);
	    }
	} /* for */
#ifdef MSDOS
      if (disk != destDisk) SETDRIVE(destDisk, &ndrives);
#endif
    } /* if args */

  getcwd(homedir, sizeof(homedir));
  FindDirs( path );

#ifdef MSDOS
  /* return to home disk */
  chdir(homedir);
  if (disk != destDisk) SETDRIVE(disk, &ndrives);
#endif

  EXIT(EXIT_SUCCESS);
}
