/* $Id: fnsplit.c,v 1.1 2001/08/10 21:19:05 richdawe Exp $ */

/*
 *  fnsplit.c - Decompose a path into its components
 *  Copyright (C) 2000, 2001 by Richard Dawe
 *      
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "common.h"

#ifndef HAVE_FNSPLIT

#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include "fnsplit.h"

/* Support for test harness */
#ifdef TEST

/* If testing, force MS-DOS style drive letter parsing. */
#undef MAXDRIVE
#define MAXDRIVE 3

#endif /* TEST */

/* -----------
 * - fnsplit -
 * ----------- */

/*
 * This function splits 'path' into its various components. 'drive', 'dir',
 * 'name' and 'ext' should be big enough to hold any result - MAXDRIVE,
 * MAXDIR, MAXNAME and MAXEXT bytes in size, respectively. These sizes
 * include the trailing nul, which fnsplit() writes.
 *
 * It returns a bitwise OR of DRIVE, DIRECTORY, FILENAME, EXTENSION and
 * WILDCARDS, depending on what's found. On failure, 0 is returned.
 */

int
fnsplit (const char *path, char *drive, char *dir,
	 char *name, char *ext)
{
  int comps = 0; /* Components found */
  char *p = (char *) path;
  char *q = NULL;

  /* No string => duh! */
  if ((path == NULL) || (*path == '\0'))
    return(0);

  /* Path too long? */
  if (strlen(path) > MAXPATH)
    return(0);

  /* Zero strings in advance */
  if (drive != NULL)
    *drive = '\0';

  if (dir != NULL)
    *dir = '\0';

  if (name != NULL)
    *name = '\0';

  if (ext != NULL)
    *ext = '\0';

  /* TODO: Fix up the path, to eliminate stuff like '//.././'? */

  /* Wildcards */
  if ((strchr(path, '*') != NULL) || (strchr(path, '?') != NULL))
    comps |= WILDCARDS;

  /* Drive component */
#if MAXDRIVE > 0
  if ((strlen(path) >= (MAXDRIVE - 1)) && (strchr(path, ':') != NULL)) {
    if (path[MAXDRIVE - 2] == ':') {
      if (drive != NULL) {
	strncpy(drive, path, MAXDRIVE - 1);
	drive[MAXDRIVE - 1] = '\0';
      }

      comps |= DRIVE;

      /* Skip drive component */
      p += MAXDRIVE - 1;
    } else {
      /* Drive separator not in expected position => error. */
      return(0);
    }
  }
#endif /* MAXDRIVE > 0 */

  /* Directory component */
  /* NB: p should be pointing at the first slash now, if there is one. */
  q = strrchr(p, '/');
  if ((*p == '/') && (q != NULL)) {
    ptrdiff_t cnt = q - p + 1; /* Include slash */

    if (cnt > (MAXDIR - 1)) {
      /* Too long => error */
      return(0);
    }

    if (dir != NULL) {
      strncpy(dir, p, cnt);
      dir[cnt] = '\0';
    }

    comps |= DIRECTORY;

    /* Skip directory component */
    p += cnt;
  }

  /* File name component */
  if (*p != '\0') {
    q = strrchr(p, '.');
    if ((q == NULL)) {
      /* No extension */
      if (strlen(p) > (MAXFILE - 1)) {
	/* Too long => error */
	return(0);
      }

      if (name != NULL)
	strcpy(name, p);

      comps |= FILENAME;
    
      /* Skip file name component - should now point at terminating nul. */
      p += strlen(p);
    } else {
      /* Extension */
      ptrdiff_t cnt = q - p; /* Don't include '.' */
      
      if (cnt > (MAXFILE - 1)) {
	/* Too long => error */
	return(0);
      }

      if (name != NULL) {
	strncpy(name, p, cnt);
	name[cnt] = '\0';
      }

      comps |= FILENAME;

      /* Skip name component */
      p += cnt;
    }
  }

  /* Extension component */
  if (*p == '.') {
    if (strlen(p) > (MAXEXT - 1)) {
      /* Too long => error */
      return(0);
    }

    if (ext != NULL)
      strcpy(ext, p);  

    comps |= EXTENSION;
  }

  return(comps);
}

/* --- Test harness --- */
#ifdef TEST

#include <stdio.h>

/* Test cases for fnsplit() */
typedef struct  {
  const char *str;
  const int   comps;
} test_case_t;

/* TODO: Test cases with more than one directory in the path; longer names. */
static test_case_t test_cases[] = {
  { "?", FILENAME|WILDCARDS },
  { "*", FILENAME|WILDCARDS },
  { "c:", DRIVE },
  { "c:/", DRIVE|DIRECTORY },
  { "c:/temp", DRIVE|DIRECTORY|FILENAME },
  { "c:/autoexec.bat", DRIVE|DIRECTORY|FILENAME|EXTENSION },
  { "c:/*.bat", DRIVE|DIRECTORY|FILENAME|EXTENSION|WILDCARDS },
  { "/", DIRECTORY },
  { "/temp", DIRECTORY|FILENAME },
  { "/autoexec.bat", DIRECTORY|FILENAME|EXTENSION },
  { "/*.bat", DIRECTORY|FILENAME|EXTENSION|WILDCARDS },
  { NULL, 0 }
};

/* --------------
 * - test_match -
 * -------------- */

static int inline
test_match (const char *path, const char *drive, const char *dir,
	    const char *name, const char *ext)
{
  static char buf[MAXDRIVE + MAXDIR + MAXFILE + MAXEXT];

  memset(buf, 0, sizeof(buf));

  if (drive != NULL)
    strcat(buf, drive);

  if (dir != NULL)
    strcat(buf, dir);

  if (name != NULL)
    strcat(buf, name);

  if (ext != NULL)
    strcat(buf, ext);

  if (strcmp(path, buf) == 0)
    return(1);

  /* Fail by default */
  return(0);
}

/* --------
 * - main -
 * -------- */

int
main (int argc, char *argv[])
{
  char drive[MAXDRIVE], dir[MAXDIR], name[MAXFILE], ext[MAXEXT];
  int comps;
  int i;

  for (i = 0; test_cases[i].str != NULL; i++) {
    comps = fnsplit(test_cases[i].str, drive, dir, name, ext);
    if (comps != test_cases[i].comps) {
      fprintf(stderr,
	      "Test case %d failed: %s\n"
	      "Expected 0x%x components, got 0x%x components\n",
	      i + 1, test_cases[i].str, test_cases[i].comps, comps);
      continue;
    }

    if (!test_match(test_cases[i].str, drive, dir, name, ext)) {
      fprintf(stderr, "Disassembly failed: %s -> %s%s%s%s\n",
	      test_cases[i].str, drive, dir, name, ext);
      continue;
    }

    printf("OK: %s -> %s%s%s%s\n",
	   test_cases[i].str, drive, dir, name, ext);
  }
}

#endif /* TEST */

#endif /* !HAVE_FNSPLIT */
