Code:
/*****************************************************************************
*
* $Id$
*
* Kornshell 93 builtin function 'setpath'.
*
*****************************************************************************/
#include <shell.h>
/*-------------*/
/* definitions */
/*-------------*/
#define DELIMITER ':'
#define DEFAULTVAR "PATH"
/*-----------------------------------------*/
/* embedded manpage and options definition */
/*-----------------------------------------*/
static char optget_opts[] =
"[+NAME?setpath - manipulate PATH type variables]"
"[-AUTHOR?Paul Herger <ph@herger.at>]"
"[+DESCRIPTION?\bsetpath\b manipulates $PATH or other PATH type shell "
"variables.]"
"[+?The names of directories or files can be "
"appended to the end (option \b-a\b), "
"prepended to the beginning (option \b-p\b) or "
"excluded (option \b-x\b) from the variable. "
"These options are mutually exclusive.]"
"[+?After these manipulations, duplicate entries can be removed from the "
"variable if the \b-u\b option is specified. Each first occurence of "
"duplicate entries is kept while all others are deleted.]"
"[+?If the \b-e\b option is specified, all directories or files that do not "
"exists are deleted.]"
"[a?Append to the end of the variable]"
"[p?Prepend to the beginning of the variable]"
"[x?Exclude from the variable]"
"[e?Delete from or do not include into the path variable non existing"
"directories or files]"
"[u?Make elements unique by deleting all but the first occurence of"
"every directory or file]"
"[v?Use \avname\a instead of $PATH]:[vname]"
"\n"
"\n[path ...]\n"
"\n"
"[+EXIT STATUS?]{"
"[+0?Completed successfully.]"
"[+>0?An error occurred.]}"
"[+EXAMPLE?\b$ setpath -aeu /opt/bin /usr/local/bin\b]"
"[+?Both /opt/bin and /usr/local/bin are appended at the end of the shell "
"variable $PATH, if they are not yet elements of it. If they do not "
"exist they are not appended to or are deleted from $PATH.]"
"[+?\b$ setpath -xv MANPATH /opt/man\b]"
"[+?/opt/man is deleted from $MANPATH.]"
"[+?\b$ setpath -ue\b]"
"[+?Duplicate entries and entries that do not refer to an existing "
"filesystem object are deleted from $PATH.]";
/*----------------------*/
/* other error messages */
/*----------------------*/
static char usage[] = "[{-a|-p|-x}] [-eu] [-v vname] [path ...]";
static char err_internal[] = "internal error";
static char err_mutual[] = "options -%c and -%c are mutually exclusive";
/*===========================================================================*/
/* Add a copy of a string to a container (set or queue). If the string */
/* contains delimiters, split the string at the delimiters and add each part */
/* of the string as independent string to the container, thus splitting PATH */
/* type strings in their components. */
/*===========================================================================*/
static void add_to_container(Dt_t *container, char *string)
{
char *copy, *ptr;
if (!(copy = stkcopy(stkstd, string))) return;
dtinsert(container, copy);
for (ptr = copy; *ptr; ptr++)
if (*ptr == DELIMITER)
{
*ptr = '\0';
dtinsert(container, ptr+1);
}
}
/*===========================================================================*/
/* This is the main routine of the 'setpath' builtin. It processes the */
/* arguments, sets up containers and builts the result on the stack. */
/*===========================================================================*/
int b_setpath(int argc, char *argv[], void *context)
{
Shell_t *shp = (Shell_t*)context;
Namval_t *np;
Dtdisc_t disc = {0, 0, -1};
Dt_t *dt_old, *dt_del;
char ch, action = '\0', *obj, *vname = NULL;
int flag_e = 0, flag_u = 0, output = 0, stktop;
/*-----------------------*/
/* process the arguments */
/*-----------------------*/
while (ch = optget(argv, optget_opts))
switch (ch)
{
/*-------------------*/
/* action to perform */
/*-------------------*/
case 'a':
case 'p':
case 'x':
if (action)
error(ERROR_ERROR, err_mutual, action, ch);
else
action = ch;
break;
/*--------------------------*/
/* what variable to process */
/*--------------------------*/
case 'v':
if (vname)
error(ERROR_WARNING, "option -v can only be used "
"once, ignoring -v %s", opt_info.arg);
else
vname = opt_info.arg;
break;
/*-------------*/
/* other flags */
/*-------------*/
case 'e':
flag_e = 1;
break;
case 'u':
flag_u = 1;
break;
/*----------------------*/
/* error and usage info */
/*----------------------*/
case ':':
error(2, "%s", opt_info.arg);
break;
case '?':
error(ERROR_usage(2), "%s", opt_info.arg);
break;
}
/*-----------------------------*/
/* finish processing arguments */
/*-----------------------------*/
argv += opt_info.index;
argc -= opt_info.index;
if (error_info.errors) error(ERROR_usage(2), "%s", usage);
if (argc == 0 && flag_e == 0 && flag_u == 0) return 0;
if (!vname) vname = DEFAULTVAR;
/*---------------------------------------------------------------------*/
/* create containers and get current content of the processed variable */
/*---------------------------------------------------------------------*/
dt_old = dtopen (&disc, Dtqueue);
dt_del = dtopen (&disc, Dtset);
np = nv_open(vname, shp->var_tree, NV_VARNAME);
if (!dt_old || !dt_del || !np) error(ERROR_exit(1), err_internal);
/*-------------------------------------------------------------*/
/* fill containers with arguments and current variable content */
/*-------------------------------------------------------------*/
if (action == 'p')
while (argc-- > 0) add_to_container(dt_old, *argv++);
add_to_container (dt_old, (char *) nv_getval(np));
if (action == 'a')
while (argc-- > 0) add_to_container(dt_old, *argv++);
if (action == 'x')
while (argc-- > 0) add_to_container(dt_del, *argv++);
/*---------------------------------------------------------------*/
/* now copy entries from 'dt_old' to the stack for output */
/* - skip elements in 'dt_del' (options '-x' and '-u') */
/* - check for existence if option '-e' is set */
/* - insert into 'dt_del' if option '-u' is set */
/* - if all tests passed copy to the stack to build the new path */
/*---------------------------------------------------------------*/
stktop = stktell(stkstd);
for (obj = dtfirst(dt_old); obj; obj = dtnext(dt_old, obj))
{
if (dtmatch(dt_del, obj)) continue;
if (flag_e && *obj && *obj != '.' && access(obj, F_OK) < 0) continue;
if (flag_u) dtinsert(dt_del, obj);
if (output) sfputc(stkstd, DELIMITER);
sfprintf(stkstd, "%s", obj);
output = 1;
}
/*--------------------------------------------------*/
/* copy the result string to the processed variable */
/*--------------------------------------------------*/
sfputc(stkstd, '\0');
nv_putval(np, stkptr(stkstd, stktop), 0);
nv_close(np);
dtclose(dt_old);
dtclose(dt_del);
return 0;
}