/*
 * glob.c
 *
 * Full file wildcard matching
 *
 *  1998 Straylight/Edgeware
 */

/*----- Licensing note ----------------------------------------------------*
 *
 * This file is part of Straylight's core utilities (coreutils).
 *
 * Coreutils 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, or (at your option)
 * any later version.
 *
 * Coreutils 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 coreutils.  If not, write to the Free Software Foundation,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*----- Header files ------------------------------------------------------*/

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

#include "swis.h"
#include "swiv.h"

#include "alloc.h"
#include "gf.h"
#include "glob.h"

/*----- Type definitions --------------------------------------------------*/

typedef struct glob_ctx {
  char buf[1024];			/* Pointer to output buffer */
  void (*proc)(const char *, void *);	/* User procedure */
  void *ctx;				/* Context to pass the procedure */
  int done;				/* Number of matches found */
} glob_ctx;

/*----- Main code ---------------------------------------------------------*/

/* --- Wildcard syntax --- *
 *
 * The wildcards `*' and `#' do what they normally do: match zero-or-more
 * and any-one characters respectively.  Additionally, a `.' where a
 * filename is expected will search recursively down the directory
 * structure.  Only globby matches with really existing things are counted;
 * if a name contains nothing globbable then it matches nothing.  This is
 * done for speed: I expect the client to use a name literally if it fails
 * to match anything.  The syntax is a sort of blend between traditional
 * RISC OS and kpathsea.
 */

/* --- Hack note --- *
 *
 * This will fail gloriously given something like `adfs::ak*ha.$.foo.*'.
 * Do I look like I care?  This program has already taken five times longer
 * than it should have done because I decided I wanted to do wildcarding.
 */

/* --- Forward reference --- */

static void glob_do(glob_ctx *g, char *p, char *in);

/* --- @glob_got@ --- *
 *
 * Arguments:	@glob_ctx *g@ = pointer to my context
 *		@char *p@ = pointer to current position in buffer
 *		@char *fn@ = filename to add
 *		@char *in@ = rest of wildcard pattern to match
 *
 * Returns:	---
 *
 * Use:		Handles a matched filename in the glob matcher.
 */

static void glob_got(glob_ctx *g, char *p, char *fn, char *in)
{
  size_t sz;

  /* --- Build the filename --- */

  if (p != g->buf)
    *p++ = '.';
  sz = strlen(fn);
  memcpy(p, fn, sz + 1);
  p += sz;

  /* --- See if this is the final element --- */

  if (!in) {
    int ty;

    if (_swix(OS_File, _inr(0, 1) | _out(0), 17, g->buf, &ty) || !ty)
      return;
    g->done++;
    g->proc(g->buf, g->ctx);
  } else
    glob_do(g, p, in);
}

/* --- @glob_match@ --- *
 *
 * Arguments:	@const char *pat@ = pointer to pattern string
 *		@const char *s@ = pointer to candidate string
 *
 * Returns:	Nonzero if pattern matches candidate.
 *
 * Use:		Tries to match globbily.  This is very simple stuff.
 *		I may add character classes later if I feel really eager.
 */

static int glob_match(const char *pat, const char *s)
{
  for (;;) {
    if (!*pat && !*s)
      return (1);
    else if (!*pat)
      return (0);
    else if (*pat == '*') {
      do pat++; while (*pat == '*');
      do {
        if (glob_match(pat, s))
          return (1);
      } while (*s++);
      return (0);
    } else if (!*s)
      return (0);
    else if (*pat != '#' && tolower(*pat) != tolower(*s))
      return (0);
    else
      pat++, s++;
  }
}

/* --- @glob_do@ --- *
 *
 * Arguments:	@glob_ctx *g@ = pointer to my context
 *		@char *p@ = pointer to current position in buffer
 *		@char *in@ = rest of wildcard pattern to match
 *
 * Returns:	---
 *
 * Use:		Main recursive glob matcher.
 */

static void glob_do(glob_ctx *g, char *p, char *in)
{
  char *q;
  char *rec = 0;

  /* --- Pick out the next component of the pathname --- */

  if (*in == '.') {
    do in++; while (*in == '.');
    rec = in - 1;
    if (!*in) in = "*";
  }

  q = strchr(in, '.');
  if (q)
    *q++ = 0;
  else
    q = 0;

  /* --- See if this contains any wildcards --- */

  if (rec) {
    gf_ctx gx;
    char *n;
    char *qq = q ? q - 1 : 0;
    for (gf_init(&gx, "*", g->buf); *p = 0, (n = gf_next(&gx)) != 0; ) {
      if (qq) *qq = 0;
      if (glob_match(in, n))
        glob_got(g, p, n, q);
      if (qq) *qq = '.';
      glob_got(g, p, n, rec);
    }
  } else if (strpbrk(in, "*#")) {
    gf_ctx gx;
    char *n;
    for (gf_init(&gx, in, g->buf); *p = 0, (n = gf_next(&gx)) != 0; )
      glob_got(g, p, n, q);
  } else
    glob_got(g, p, in, q);
}

/* --- @glob@ --- *
 *
 * Arguments:	@const char *pat@ = pointer to pattern string to match
 *		@void (*proc)(const char *, void *)@ = client function
 *		@void *ctx@ = context pointer to pass function
 *
 * Returns:	Number of filenames matched.
 *
 * Use:		Does filename globbing.
 */

int glob(const char *pat, void (*proc)(const char *, void *), void *ctx)
{
  glob_ctx gx;
  char *in;

  if (!strpbrk(pat, "*#") && !strstr(pat, ".."))
    return (0);

  gx.proc = proc;
  gx.ctx = ctx;
  gx.done = 0;
  in = xstrdup(pat);
  glob_do(&gx, gx.buf, in);
  free(in);
  return (gx.done);
}

/*----- That's all, folks -------------------------------------------------*/
