/*
 * addpath: add components to your $PATH
 */
static const char rcsid[] = "Mastodon $Id: s.addpath.c 1.5 00/05/21 16:33:42-00:00 orc $";

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

/*
 * usage: addpath [-abV?] [-P path] dir [...]
 */

#include <basis/options.h>

enum { O_SUFFIX=1, O_PREFIX, O_REORDER, O_PATH, O_HELP, O_VERSION };

struct x_option options[] = {
    { O_SUFFIX,	'a', "after",	0,	"Add new directories to the end of the path." },
    { O_PREFIX,	'b', "before",	0,	"Add new directories to the start of the path." },
    { O_REORDER,'r', "reorder",	0,	"Reorder existing directories." },
    { O_PATH,	'P', "path",	"PATH",	"Use this path instead of $PATH." },
    { O_HELP,	'h', "help",	0,	"Give this message" },
    { O_VERSION,'V', "version", 0,	"Give the tool version, then exit." },
};
#define NROPTS	(sizeof options/sizeof options[0])

static void
Usage(char **argv, int verbose)
{
    char *program;

    if ((program = strrchr(argv[0], '/')) != 0)
	++program;
    else
	program = argv[0];

    if (verbose) {
	printf("Usage: %s [option list] <path>\n\n", program);
        showopts(stdout, NROPTS, options);
	exit(0);
    }
    else {
	fprintf(stderr, "Usage: %s [option list] <path>\n\n", program);
        fprintf(stderr, "type ``%s --help'' for more information\n", program);
	exit(1);
    }
} /* Usage */


/*
 * fail() -- when we're out of memory, spit out the current path,
 *           complain bitterly, then fail.
 */
static void
fail()
{
    char *path = getenv("PATH");

    perror("addpath");
    if (path)
	puts(path);
    exit(1);
} /* fail */


void
main(int argc, char **argv)
{
    int before = 0;	/* put new directories at the start of the path */
    int reorder = 0;	/* reorder components in the $PATH */
    char *path;		/* our path -- defaulting to $PATH, of course */
    char *p;		/* scratch pointer */
    char **dirs;	/* array of directories in $PATH, broken out */
    int nrdirs;		/* number of directories in $PATH */
    char **newdirs;	/* array of new directories, from arglist */
    int nrnewdirs;	/* how many new dirs do we have? */
    int ix, id;		/* two scratch indexes */
    int need = 0;	/* nonzero if we need to spit out `:' or `\n' */

    int opt;		/* for getopt() */

    x_opterr = 1;			/* we want getopt() to complain about bogus opts */

    while ((opt = x_getopt(argc, argv, NROPTS, options)) != EOF) {
	switch (opt) {
	case O_PREFIX:	before = 1;
			break;
	case O_SUFFIX:	before = 0;
			break;
	case O_REORDER:	reorder = 1;
			break;
	case O_PATH:	path = x_optarg;
			break;
	case O_VERSION:	printf("Addpath: %s\n", rcsid);
			exit(0);
	case O_HELP:	Usage(argv, 1);
	default:	Usage(argv, 0);
	}
    }

    /* allocate dirs[] from the contents of our selected $PATH
     */
    nrdirs = 0;
    if ((p = getenv("PATH")) != 0) {
	if ((path = strdup(p)) == 0 || (dirs = malloc(1)) == 0)
	    fail();
	for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
	    dirs = realloc(dirs, (1+nrdirs) * sizeof dirs[0]);
	    if (dirs == 0)
		fail();
	    dirs[nrdirs] = p;
	    nrdirs++;
	}
    }
    else {
	if ( (dirs=malloc(sizeof *dirs)) == 0)
	    fail();
	dirs[nrdirs] = 0;
    }


    /* allocate newdirs[] from the argument list
     */
    if ( (newdirs = malloc(1)) == 0)
	fail();
    nrnewdirs = 0;
    for (ix = x_optind; ix < argc; ix++)
	for (p = strtok(argv[ix], ":"); p; p = strtok(0, ":")) {
	    newdirs = realloc(newdirs, (1+nrdirs) * sizeof dirs[0]);
	    if (newdirs == 0)
		fail();
	    newdirs[nrnewdirs] = p;
	    nrnewdirs++;
	}


    /* a note on need:  We don't automatically print `:' or `\n' -- we simply toggle need
     * on whenever we do output, so the next thing that does output can put in the separator
     * it feels is necessary to properly partition the $PATH
     */

    /* if we want to reorder the path, weed out $PATH entries that
     * are also arguments to addpath
     */
    if (reorder) {
	for (id=0; id<nrdirs; id++)
	    for (ix = 0; ix < nrnewdirs; ix++)
		if (dirs[id] && strcmp(dirs[id], newdirs[ix]) == 0)
		    dirs[id] = 0;
    }

    /* if -a flag (default), dump the existing $PATH first
     */
    if (!before)
	for (ix=0; ix < nrdirs; ix++)
	    if (dirs[ix]) {
		if (need)
		    putchar(':');
		fputs(dirs[ix], stdout);
		need=1;
	    }

    /* dump the named arguments without replacing directories that are already
     * in the $PATH.
     */
    for (ix = 0; ix < nrnewdirs; ix++) {
	for (id = 0; id < nrdirs; id++)
	    if (dirs[id] && strcmp(dirs[id], newdirs[ix]) == 0)
		break;

	if (id >= nrdirs) {
	    if (need)
		putchar(':');
	    fputs(newdirs[ix], stdout);
	    need = 1;
	}
    }

    /* if -b flag, dump the $PATH after the new arguments */
    if (before)
	for (ix = 0; ix < nrdirs; ix++)
	    if (dirs[ix]) {
		if (need)
		    putchar(':');
		fputs(dirs[ix], stdout);
		need = 1;
	    }

    /* if we printed anything out, we should do \n to terminate this line
     * and make everything beautiful
     */
    if (need)
	putchar('\n');
} /* addpath */
