mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-09-04 10:41:11 +02:00
When deciding whether there is enough horizontal space to show completion descriptions for each match in a listing, treat the separator as part of the description rather than as part of the match, and account for lines that have already wrapped due to very long matches.
4982 lines
120 KiB
C
4982 lines
120 KiB
C
/*
|
|
* computil.c - completion utilities
|
|
*
|
|
* This file is part of zsh, the Z shell.
|
|
*
|
|
* Copyright (c) 1999 Sven Wischnowsky
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, without written agreement and without
|
|
* license or royalty fees, to use, copy, modify, and distribute this
|
|
* software and to distribute modified versions of this software for any
|
|
* purpose, provided that the above copyright notice and the following
|
|
* two paragraphs appear in all copies of this software.
|
|
*
|
|
* In no event shall Sven Wischnowsky or the Zsh Development Group be liable
|
|
* to any party for direct, indirect, special, incidental, or consequential
|
|
* damages arising out of the use of this software and its documentation,
|
|
* even if Sven Wischnowsky and the Zsh Development Group have been advised of
|
|
* the possibility of such damage.
|
|
*
|
|
* Sven Wischnowsky and the Zsh Development Group specifically disclaim any
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
* merchantability and fitness for a particular purpose. The software
|
|
* provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
|
|
* Zsh Development Group have no obligation to provide maintenance,
|
|
* support, updates, enhancements, or modifications.
|
|
*
|
|
*/
|
|
|
|
#include "computil.mdh"
|
|
#include "computil.pro"
|
|
|
|
|
|
/* Help for `_describe'. */
|
|
|
|
typedef struct cdset *Cdset;
|
|
typedef struct cdstr *Cdstr;
|
|
typedef struct cdrun *Cdrun;
|
|
|
|
struct cdstate {
|
|
int showd; /* != 0 if descriptions should be shown */
|
|
char *sep; /* the separator string */
|
|
int slen; /* its metafied length */
|
|
int swidth; /* its screen width */
|
|
int maxmlen; /* maximum length to allow for the matches */
|
|
Cdset sets; /* the sets of matches */
|
|
int pre; /* longest prefix length (before description) */
|
|
int premaxw; /* ... and its screen width */
|
|
int suf; /* longest suffix (description) */
|
|
int maxg; /* size of largest group */
|
|
int maxglen; /* columns for matches of largest group */
|
|
int groups; /* number of groups */
|
|
int descs; /* number of non-group matches with desc */
|
|
int gprew; /* prefix screen width for group display */
|
|
Cdrun runs; /* runs to report to shell code */
|
|
};
|
|
|
|
struct cdstr {
|
|
Cdstr next; /* the next one in this set */
|
|
char *str; /* the string to display */
|
|
char *desc; /* the description or NULL */
|
|
char *match; /* the match to add */
|
|
char *sortstr; /* unmetafied string used to sort matches */
|
|
int len; /* length of str or match */
|
|
int width; /* ... and its screen width */
|
|
Cdstr other; /* next string with the same description */
|
|
int kind; /* 0: not in a group, 1: the first, 2: other */
|
|
Cdset set; /* the set this string is in */
|
|
Cdstr run; /* next in this run */
|
|
};
|
|
|
|
struct cdrun {
|
|
Cdrun next; /* ... */
|
|
int type; /* see CRT_* below */
|
|
Cdstr strs; /* strings in this run */
|
|
int count; /* number of strings in this run */
|
|
};
|
|
|
|
#define CRT_SIMPLE 0
|
|
#define CRT_DESC 1
|
|
#define CRT_SPEC 2
|
|
#define CRT_DUMMY 3
|
|
#define CRT_EXPL 4
|
|
|
|
struct cdset {
|
|
Cdset next; /* guess what */
|
|
char **opts; /* the compadd-options */
|
|
Cdstr strs; /* the strings/matches */
|
|
int count; /* number of matches in this set */
|
|
int desc; /* number of matches with description */
|
|
};
|
|
|
|
static struct cdstate cd_state;
|
|
static int cd_parsed = 0;
|
|
|
|
static void
|
|
freecdsets(Cdset p)
|
|
{
|
|
Cdset n;
|
|
Cdstr s, sn;
|
|
Cdrun r, rn;
|
|
|
|
for (; p; p = n) {
|
|
n = p->next;
|
|
if (p->opts)
|
|
freearray(p->opts);
|
|
for (s = p->strs; s; s = sn) {
|
|
sn = s->next;
|
|
zfree(s->sortstr, strlen(s->str) + 1);
|
|
zsfree(s->str);
|
|
zsfree(s->desc);
|
|
if (s->match != s->str)
|
|
zsfree(s->match);
|
|
zfree(s, sizeof(*s));
|
|
}
|
|
for (r = cd_state.runs; r; r = rn) {
|
|
rn = r->next;
|
|
zfree(r, sizeof(*r));
|
|
}
|
|
zfree(p, sizeof(*p));
|
|
}
|
|
}
|
|
|
|
/* Find matches with same descriptions and group them. */
|
|
|
|
static void
|
|
cd_group(int maxg)
|
|
{
|
|
Cdset set1, set2;
|
|
Cdstr str1, str2, *strp;
|
|
int num, width;
|
|
|
|
cd_state.groups = cd_state.descs = cd_state.maxglen = 0;
|
|
cd_state.maxg = 0;
|
|
|
|
for (set1 = cd_state.sets; set1; set1 = set1->next)
|
|
for (str1 = set1->strs; str1; str1 = str1->next) {
|
|
str1->kind = 0;
|
|
str1->other = NULL;
|
|
}
|
|
|
|
for (set1 = cd_state.sets; set1; set1 = set1->next) {
|
|
for (str1 = set1->strs; str1; str1 = str1->next) {
|
|
if (!str1->desc || str1->kind != 0)
|
|
continue;
|
|
|
|
num = 1;
|
|
width = str1->width + cd_state.swidth;
|
|
if (width > cd_state.maxglen)
|
|
cd_state.maxglen = width;
|
|
strp = &(str1->other);
|
|
|
|
for (set2 = set1; set2; set2 = set2->next) {
|
|
for (str2 = (set2 == set1 ? str1->next : set2->strs);
|
|
str2; str2 = str2->next)
|
|
if (str2->desc && !strcmp(str1->desc, str2->desc)) {
|
|
width += CM_SPACE + str2->width;
|
|
if (width > cd_state.maxmlen || num == maxg)
|
|
break;
|
|
if (width > cd_state.maxglen)
|
|
cd_state.maxglen = width;
|
|
str1->kind = 1;
|
|
str2->kind = 2;
|
|
num++;
|
|
*strp = str2;
|
|
strp = &(str2->other);
|
|
}
|
|
if (str2)
|
|
break;
|
|
}
|
|
*strp = NULL;
|
|
|
|
if (num > 1)
|
|
cd_state.groups++;
|
|
else
|
|
cd_state.descs++;
|
|
|
|
if (num > cd_state.maxg)
|
|
cd_state.maxg = num;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Calculate longest prefix and suffix and count the strings with
|
|
* descriptions. */
|
|
|
|
static void
|
|
cd_calc()
|
|
{
|
|
Cdset set;
|
|
Cdstr str;
|
|
int l;
|
|
|
|
cd_state.pre = cd_state.suf = 0;
|
|
|
|
for (set = cd_state.sets; set; set = set->next) {
|
|
set->count = set->desc = 0;
|
|
for (str = set->strs; str; str = str->next) {
|
|
set->count++;
|
|
if ((l = strlen(str->str)) > cd_state.pre)
|
|
cd_state.pre = l;
|
|
if ((l = MB_METASTRWIDTH(str->str)) > cd_state.premaxw)
|
|
cd_state.premaxw = l;
|
|
if (str->desc) {
|
|
set->desc++;
|
|
if ((l = strlen(str->desc)) > cd_state.suf)
|
|
cd_state.suf = l;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
cd_sort(const void *a, const void *b)
|
|
{
|
|
return zstrcmp((*((Cdstr *) a))->sortstr, (*((Cdstr *) b))->sortstr, 0);
|
|
}
|
|
|
|
static int
|
|
cd_prep()
|
|
{
|
|
Cdrun run, *runp;
|
|
Cdset set;
|
|
Cdstr str, *strp;
|
|
|
|
runp = &(cd_state.runs);
|
|
|
|
if (cd_state.groups) {
|
|
int preplines = cd_state.groups + cd_state.descs;
|
|
VARARR(Cdstr, grps, preplines);
|
|
VARARR(int, wids, cd_state.maxg);
|
|
Cdstr gs, gp, gn, *gpp;
|
|
int i, j, d;
|
|
Cdrun expl;
|
|
Cdstr *strp2;
|
|
|
|
memset(wids, 0, cd_state.maxg * sizeof(int));
|
|
strp = grps;
|
|
|
|
for (set = cd_state.sets; set; set = set->next)
|
|
for (str = set->strs; str; str = str->next) {
|
|
if (str->kind != 1) {
|
|
if (!str->kind && str->desc) {
|
|
if (str->width > wids[0])
|
|
wids[0] = str->width;
|
|
str->other = NULL;
|
|
*strp++ = str;
|
|
}
|
|
continue;
|
|
}
|
|
gs = str;
|
|
gs->kind = 2;
|
|
gp = str->other;
|
|
gs->other = NULL;
|
|
for (; gp; gp = gn) {
|
|
gn = gp->other;
|
|
gp->other = NULL;
|
|
for (gpp = &gs; *gpp && (*gpp)->width > gp->width;
|
|
gpp = &((*gpp)->other));
|
|
gp->other = *gpp;
|
|
*gpp = gp;
|
|
}
|
|
for (gp = gs, i = 0; gp; gp = gp->other, i++)
|
|
if (gp->width > wids[i])
|
|
wids[i] = gp->width;
|
|
|
|
*strp++ = gs;
|
|
}
|
|
|
|
cd_state.gprew = 0;
|
|
for (i = 0; i < cd_state.maxg; i++) {
|
|
cd_state.gprew += wids[i] + CM_SPACE;
|
|
}
|
|
|
|
if (cd_state.gprew > cd_state.maxmlen && cd_state.maxglen > 1)
|
|
return 1;
|
|
|
|
for (i = 0; i < preplines; i++) {
|
|
Cdstr s = grps[i];
|
|
int dummy;
|
|
|
|
s->sortstr = ztrdup(s->str);
|
|
unmetafy(s->sortstr, &dummy);
|
|
}
|
|
|
|
qsort(grps, preplines, sizeof(Cdstr), cd_sort);
|
|
|
|
for (i = preplines, strp = grps; i > 1; i--, strp++) {
|
|
strp2 = strp + 1;
|
|
if (!strcmp((*strp)->desc, (*strp2)->desc))
|
|
continue;
|
|
for (j = i - 2, strp2++; j > 0; j--, strp2++)
|
|
if (!strcmp((*strp)->desc, (*strp2)->desc)) {
|
|
Cdstr tmp = *strp2;
|
|
|
|
memmove(strp + 2, strp + 1,
|
|
(strp2 - strp - 1) * sizeof(Cdstr));
|
|
|
|
*++strp = tmp;
|
|
i--;
|
|
}
|
|
}
|
|
expl = (Cdrun) zalloc(sizeof(*run));
|
|
expl->type = CRT_EXPL;
|
|
expl->strs = grps[0];
|
|
expl->count = preplines;
|
|
|
|
for (i = preplines, strp = grps, strp2 = NULL; i; i--, strp++) {
|
|
str = *strp;
|
|
*strp = str->other;
|
|
if (strp2)
|
|
*strp2 = str;
|
|
strp2 = &(str->run);
|
|
|
|
*runp = run = (Cdrun) zalloc(sizeof(*run));
|
|
runp = &(run->next);
|
|
run->type = CRT_SPEC;
|
|
run->strs = str;
|
|
run->count = 1;
|
|
}
|
|
*strp2 = NULL;
|
|
|
|
for (i = cd_state.maxg - 1; i; i--) {
|
|
for (d = 0, j = preplines, strp = grps; j; j--, strp++) {
|
|
if ((str = *strp)) {
|
|
if (d) {
|
|
*runp = run = (Cdrun) zalloc(sizeof(*run));
|
|
runp = &(run->next);
|
|
run->type = CRT_DUMMY;
|
|
run->strs = expl->strs;
|
|
run->count = d;
|
|
d = 0;
|
|
}
|
|
*runp = run = (Cdrun) zalloc(sizeof(*run));
|
|
runp = &(run->next);
|
|
run->type = CRT_SPEC;
|
|
run->strs = str;
|
|
run->strs->run = NULL;
|
|
run->count = 1;
|
|
|
|
*strp = str->other;
|
|
} else
|
|
d++;
|
|
}
|
|
if (d) {
|
|
*runp = run = (Cdrun) zalloc(sizeof(*run));
|
|
runp = &(run->next);
|
|
run->type = CRT_DUMMY;
|
|
run->strs = expl->strs;
|
|
run->count = d;
|
|
}
|
|
}
|
|
*runp = expl;
|
|
runp = &(expl->next);
|
|
|
|
for (set = cd_state.sets; set; set = set->next) {
|
|
for (i = 0, gs = NULL, gpp = &gs, str = set->strs;
|
|
str; str = str->next) {
|
|
if (str->kind || str->desc)
|
|
continue;
|
|
|
|
i++;
|
|
*gpp = str;
|
|
gpp = &(str->run);
|
|
}
|
|
*gpp = NULL;
|
|
if (i) {
|
|
*runp = run = (Cdrun) zalloc(sizeof(*run));
|
|
runp = &(run->next);
|
|
run->type = CRT_SIMPLE;
|
|
run->strs = gs;
|
|
run->count = i;
|
|
}
|
|
}
|
|
} else if (cd_state.showd) {
|
|
for (set = cd_state.sets; set; set = set->next) {
|
|
if (set->desc) {
|
|
*runp = run = (Cdrun) zalloc(sizeof(*run));
|
|
runp = &(run->next);
|
|
run->type = CRT_DESC;
|
|
strp = &(run->strs);
|
|
for (str = set->strs; str; str = str->next)
|
|
if (str->desc) {
|
|
*strp = str;
|
|
strp = &(str->run);
|
|
}
|
|
*strp = NULL;
|
|
run->count = set->desc;
|
|
}
|
|
if (set->desc != set->count) {
|
|
*runp = run = (Cdrun) zalloc(sizeof(*run));
|
|
runp = &(run->next);
|
|
run->type = CRT_SIMPLE;
|
|
strp = &(run->strs);
|
|
for (str = set->strs; str; str = str->next)
|
|
if (!str->desc) {
|
|
*strp = str;
|
|
strp = &(str->run);
|
|
}
|
|
*strp = NULL;
|
|
run->count = set->count - set->desc;
|
|
}
|
|
}
|
|
} else {
|
|
for (set = cd_state.sets; set; set = set->next)
|
|
if (set->count) {
|
|
*runp = run = (Cdrun) zalloc(sizeof(*run));
|
|
runp = &(run->next);
|
|
run->type = CRT_SIMPLE;
|
|
run->strs = set->strs;
|
|
for (str = set->strs; str; str = str->next)
|
|
str->run = str->next;
|
|
run->count = set->count;
|
|
}
|
|
}
|
|
*runp = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Duplicate and concatenate two arrays. Return the result. */
|
|
|
|
static char **
|
|
cd_arrcat(char **a, char **b)
|
|
{
|
|
if (!b)
|
|
return zarrdup(a);
|
|
else {
|
|
char **r = (char **) zalloc((arrlen(a) + arrlen(b) + 1) *
|
|
sizeof(char *));
|
|
char **p = r;
|
|
|
|
for (; *a; a++)
|
|
*p++ = ztrdup(*a);
|
|
for (; *b; b++)
|
|
*p++ = ztrdup(*b);
|
|
|
|
*p = NULL;
|
|
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/* Initialisation. Store and calculate the string and matches and so on. */
|
|
|
|
static int
|
|
cd_init(char *nam, char *hide, char *mlen, char *sep,
|
|
char **opts, char **args, int disp)
|
|
{
|
|
Cdset *setp, set;
|
|
Cdstr *strp, str;
|
|
char **ap, *tmp;
|
|
int grp = 0, itmp;
|
|
|
|
if (cd_parsed) {
|
|
zsfree(cd_state.sep);
|
|
freecdsets(cd_state.sets);
|
|
cd_parsed = 0;
|
|
}
|
|
setp = &(cd_state.sets);
|
|
cd_state.sep = ztrdup(sep);
|
|
cd_state.slen = strlen(sep);
|
|
cd_state.swidth = MB_METASTRWIDTH(sep);
|
|
cd_state.sets = NULL;
|
|
cd_state.showd = disp;
|
|
cd_state.maxg = cd_state.groups = cd_state.descs = 0;
|
|
cd_state.maxmlen = atoi(mlen);
|
|
cd_state.premaxw = 0;
|
|
itmp = zterm_columns - cd_state.swidth - 4;
|
|
if (cd_state.maxmlen > itmp)
|
|
cd_state.maxmlen = itmp;
|
|
if (cd_state.maxmlen < 4)
|
|
cd_state.maxmlen = 4;
|
|
if (*args && !strcmp(*args, "-g")) {
|
|
args++;
|
|
grp = 1;
|
|
}
|
|
while (*args) {
|
|
*setp = set = (Cdset) zshcalloc(sizeof(*set));
|
|
setp = &(set->next);
|
|
*setp = NULL;
|
|
set->opts = NULL;
|
|
set->strs = NULL;
|
|
|
|
if (!(ap = get_user_var(*args))) {
|
|
zwarnnam(nam, "invalid argument: %s", *args);
|
|
zsfree(cd_state.sep);
|
|
freecdsets(cd_state.sets);
|
|
return 1;
|
|
}
|
|
for (str = NULL, strp = &(set->strs); *ap; ap++) {
|
|
*strp = str = (Cdstr) zalloc(sizeof(*str));
|
|
strp = &(str->next);
|
|
|
|
str->kind = 0;
|
|
str->other = NULL;
|
|
str->set = set;
|
|
|
|
for (tmp = *ap; *tmp && *tmp != ':'; tmp++)
|
|
if (*tmp == '\\' && tmp[1])
|
|
tmp++;
|
|
|
|
if (*tmp)
|
|
str->desc = ztrdup(rembslash(tmp + 1));
|
|
else
|
|
str->desc = NULL;
|
|
*tmp = '\0';
|
|
str->str = str->match = ztrdup(rembslash(*ap));
|
|
str->len = strlen(str->str);
|
|
str->width = MB_METASTRWIDTH(str->str);
|
|
str->sortstr = NULL;
|
|
}
|
|
if (str)
|
|
str->next = NULL;
|
|
|
|
if (*++args && **args != '-') {
|
|
if (!(ap = get_user_var(*args))) {
|
|
zwarnnam(nam, "invalid argument: %s", *args);
|
|
zsfree(cd_state.sep);
|
|
freecdsets(cd_state.sets);
|
|
return 1;
|
|
}
|
|
for (str = set->strs; str && *ap; str = str->next, ap++)
|
|
str->match = ztrdup(*ap);
|
|
|
|
args++;
|
|
}
|
|
if (hide && *hide) {
|
|
for (str = set->strs; str; str = str->next) {
|
|
if (str->str == str->match)
|
|
str->str = ztrdup(str->str);
|
|
if (hide[1] && str->str[0] == '-' && str->str[1] == '-')
|
|
strcpy(str->str, str->str + 2);
|
|
else if (str->str[0] == '-' || str->str[0] == '+')
|
|
strcpy(str->str, str->str + 1);
|
|
}
|
|
}
|
|
for (ap = args; *args &&
|
|
(args[0][0] != '-' || args[0][1] != '-' || args[0][2]);
|
|
args++);
|
|
|
|
tmp = *args;
|
|
*args = NULL;
|
|
set->opts = cd_arrcat(ap, opts);
|
|
if ((*args = tmp))
|
|
args++;
|
|
}
|
|
if (disp && grp) {
|
|
int mg = zterm_columns;
|
|
|
|
do {
|
|
cd_group(mg);
|
|
mg = cd_state.maxg - 1;
|
|
cd_calc();
|
|
} while (cd_prep());
|
|
|
|
} else {
|
|
cd_calc();
|
|
cd_prep();
|
|
}
|
|
cd_parsed = 1;
|
|
return 0;
|
|
}
|
|
|
|
/* Copy an array with one element in reserve (at the beginning). */
|
|
|
|
static char **
|
|
cd_arrdup(char **a)
|
|
{
|
|
char **r = (char **) zalloc((arrlen(a) + 2) * sizeof(char *));
|
|
char **p = r + 1;
|
|
|
|
while (*a)
|
|
*p++ = ztrdup(*a++);
|
|
*p = NULL;
|
|
|
|
return r;
|
|
}
|
|
|
|
/* Get the next set. */
|
|
|
|
static int
|
|
cd_get(char **params)
|
|
{
|
|
Cdrun run;
|
|
|
|
if ((run = cd_state.runs)) {
|
|
Cdstr str;
|
|
char **mats, **mp, **dpys, **dp, **opts, *csl = "";
|
|
|
|
cd_state.runs = run->next;
|
|
|
|
switch (run->type) {
|
|
case CRT_SIMPLE:
|
|
mats = mp = (char **) zalloc((run->count + 1) * sizeof(char *));
|
|
dpys = dp = (char **) zalloc((run->count + 1) * sizeof(char *));
|
|
|
|
for (str = run->strs; str; str = str->run) {
|
|
*mp++ = ztrdup(str->match);
|
|
*dp++ = ztrdup(str->str ? str->str : str->match);
|
|
}
|
|
*mp = *dp = NULL;
|
|
opts = zarrdup(run->strs->set->opts);
|
|
if (cd_state.groups) {
|
|
/* We are building a columnised list with dummy matches
|
|
* but there are also matches without descriptions.
|
|
* Those end up in a different group, so make sure that
|
|
* group doesn't have an explanation. */
|
|
|
|
for (mp = dp = opts; *mp; mp++) {
|
|
if (dp[0][0] == '-' && dp[0][1] == 'X') {
|
|
if (!dp[0][2] && dp[1])
|
|
mp++;
|
|
} else
|
|
*dp++ = *mp;
|
|
}
|
|
*dp = NULL;
|
|
}
|
|
break;
|
|
|
|
case CRT_DESC:
|
|
{
|
|
/*
|
|
* The buffer size:
|
|
* max prefix length (cd_state.pre) +
|
|
* max padding (cd_state.premaxw generously :) +
|
|
* separator length (cd_state.slen) +
|
|
* inter matches gap (CM_SPACE) +
|
|
* max description length (cd_state.suf) +
|
|
* trailing \0
|
|
*/
|
|
VARARR(char, buf,
|
|
cd_state.pre + cd_state.suf +
|
|
cd_state.premaxw + cd_state.slen + 3);
|
|
mats = mp = (char **) zalloc((run->count + 1) * sizeof(char *));
|
|
dpys = dp = (char **) zalloc((run->count + 1) * sizeof(char *));
|
|
|
|
for (str = run->strs; str; str = str->run) {
|
|
char *p = buf, *pp, *d;
|
|
int l, remw, w;
|
|
|
|
*mp++ = ztrdup(str->match);
|
|
strcpy(p, str->str);
|
|
p += str->len;
|
|
memset(p, ' ', (l = (cd_state.premaxw - str->width + CM_SPACE)));
|
|
p += l;
|
|
|
|
remw = zterm_columns - cd_state.premaxw -
|
|
cd_state.swidth - 3;
|
|
while (remw < 0 && zterm_columns) {
|
|
/* line wrapped, use remainder of the extra line */
|
|
remw += zterm_columns;
|
|
}
|
|
if (cd_state.slen < remw) {
|
|
strcpy(p, cd_state.sep);
|
|
p += cd_state.slen;
|
|
remw -= cd_state.slen;
|
|
|
|
/*
|
|
* copy a character at once until no more screen
|
|
* width is available. Leave 1 character at the
|
|
* end of screen as safety margin
|
|
*/
|
|
d = str->desc;
|
|
w = MB_METASTRWIDTH(d);
|
|
if (w <= remw)
|
|
strcpy(p, d);
|
|
else {
|
|
pp = p;
|
|
while (remw > 0 && *d) {
|
|
l = MB_METACHARLEN(d);
|
|
memcpy(pp, d, l);
|
|
pp[l] = '\0';
|
|
w = MB_METASTRWIDTH(pp);
|
|
if (w > remw) {
|
|
*pp = '\0';
|
|
break;
|
|
}
|
|
|
|
pp += l;
|
|
d += l;
|
|
remw -= w;
|
|
}
|
|
}
|
|
}
|
|
|
|
*dp++ = ztrdup(buf);
|
|
}
|
|
*mp = *dp = NULL;
|
|
opts = cd_arrdup(run->strs->set->opts);
|
|
opts[0] = ztrdup("-l");
|
|
break;
|
|
}
|
|
|
|
case CRT_SPEC:
|
|
mats = (char **) zalloc(2 * sizeof(char *));
|
|
dpys = (char **) zalloc(2 * sizeof(char *));
|
|
mats[0] = ztrdup(run->strs->match);
|
|
dpys[0] = ztrdup(run->strs->str);
|
|
mats[1] = dpys[1] = NULL;
|
|
opts = cd_arrdup(run->strs->set->opts);
|
|
for (dp = opts + 1; *dp; dp++)
|
|
if (dp[0][0] == '-' && dp[0][1] == 'J')
|
|
break;
|
|
if (*dp) {
|
|
char *s = tricat("-2V", "", dp[0] + 2);
|
|
|
|
zsfree(*dp);
|
|
*dp = s;
|
|
|
|
memmove(opts, opts + 1,
|
|
(arrlen(opts + 1) + 1) * sizeof(char *));
|
|
|
|
} else
|
|
opts[0] = ztrdup("-2V-default-");
|
|
csl = "packed";
|
|
break;
|
|
|
|
case CRT_DUMMY:
|
|
{
|
|
char buf[20];
|
|
|
|
sprintf(buf, "-E%d", run->count);
|
|
|
|
mats = (char **) zalloc(sizeof(char *));
|
|
dpys = (char **) zalloc(sizeof(char *));
|
|
mats[0] = dpys[0] = NULL;
|
|
|
|
opts = cd_arrdup(run->strs->set->opts);
|
|
opts[0] = ztrdup(buf);
|
|
|
|
csl = "packed";
|
|
}
|
|
break;
|
|
|
|
default: /* This silences the "might be used uninitialized" warnings */
|
|
case CRT_EXPL:
|
|
{
|
|
/* add columns as safety margin */
|
|
VARARR(char, dbuf, cd_state.suf + cd_state.slen +
|
|
zterm_columns);
|
|
char buf[20], *p, *pp, *d;
|
|
int i = run->count, remw, w, l;
|
|
|
|
sprintf(buf, "-E%d", i);
|
|
|
|
mats = (char **) zalloc(sizeof(char *));
|
|
dpys = (char **) zalloc((i + 1) * sizeof(char *));
|
|
|
|
for (dp = dpys, str = run->strs; str; str = str->run) {
|
|
if (str->run && !strcmp(str->desc, str->run->desc)) {
|
|
*dp++ = ztrdup("");
|
|
continue;
|
|
}
|
|
|
|
strcpy(dbuf, cd_state.sep);
|
|
remw = zterm_columns - cd_state.gprew -
|
|
cd_state.swidth - CM_SPACE;
|
|
p = pp = dbuf + cd_state.slen;
|
|
d = str->desc;
|
|
w = MB_METASTRWIDTH(d);
|
|
if (w <= remw) {
|
|
strcpy(p, d);
|
|
remw -= w;
|
|
pp += strlen(d);
|
|
} else
|
|
while (remw > 0 && *d) {
|
|
l = MB_METACHARLEN(d);
|
|
memcpy(pp, d, l);
|
|
pp[l] = '\0';
|
|
w = MB_METASTRWIDTH(pp);
|
|
if (w > remw) {
|
|
*pp = '\0';
|
|
break;
|
|
}
|
|
|
|
pp += l;
|
|
d += l;
|
|
remw -= w;
|
|
}
|
|
|
|
while (remw-- > 0)
|
|
*pp++ = ' ';
|
|
*pp = '\0';
|
|
|
|
*dp++ = ztrdup(dbuf);
|
|
}
|
|
mats[0] = *dp = NULL;
|
|
|
|
opts = cd_arrdup(run->strs->set->opts);
|
|
opts[0] = ztrdup(buf);
|
|
|
|
csl = "packed";
|
|
}
|
|
break;
|
|
}
|
|
setsparam(params[0], ztrdup(csl));
|
|
setaparam(params[1], opts);
|
|
setaparam(params[2], mats);
|
|
setaparam(params[3], dpys);
|
|
|
|
zfree(run, sizeof(*run));
|
|
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**/
|
|
static int
|
|
bin_compdescribe(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
int n = arrlen(args);
|
|
|
|
if (incompfunc != 1) {
|
|
zwarnnam(nam, "can only be called from completion function");
|
|
return 1;
|
|
}
|
|
if (!args[0][0] || !args[0][1] || args[0][2]) {
|
|
zwarnnam(nam, "invalid argument: %s", args[0]);
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'i':
|
|
if (n < 3) {
|
|
zwarnnam(nam, "not enough arguments");
|
|
|
|
return 1;
|
|
}
|
|
return cd_init(nam, args[1], args[2], "", NULL, args + 3, 0);
|
|
case 'I':
|
|
if (n < 6) {
|
|
zwarnnam(nam, "not enough arguments");
|
|
|
|
return 1;
|
|
} else {
|
|
char **opts;
|
|
|
|
if (!(opts = getaparam(args[4]))) {
|
|
zwarnnam(nam, "unknown parameter: %s", args[4]);
|
|
return 1;
|
|
}
|
|
return cd_init(nam, args[1], args[2], args[3], opts, args + 5, 1);
|
|
}
|
|
case 'g':
|
|
if (cd_parsed) {
|
|
if (n != 5) {
|
|
zwarnnam(nam, (n < 5 ? "not enough arguments" :
|
|
"too many arguments"));
|
|
return 1;
|
|
}
|
|
return cd_get(args + 1);
|
|
} else {
|
|
zwarnnam(nam, "no parsed state");
|
|
return 1;
|
|
}
|
|
}
|
|
zwarnnam(nam, "invalid option: %s", args[0]);
|
|
return 1;
|
|
}
|
|
|
|
/* Help for `_arguments'. */
|
|
|
|
typedef struct cadef *Cadef;
|
|
typedef struct caopt *Caopt;
|
|
typedef struct caarg *Caarg;
|
|
|
|
/* Cache for a set of _arguments-definitions. */
|
|
|
|
struct cadef {
|
|
Cadef next; /* next in cache */
|
|
Cadef snext; /* next set */
|
|
Caopt opts; /* the options */
|
|
int nopts, ndopts, nodopts; /* number of options/direct/optional direct */
|
|
Caarg args; /* the normal arguments */
|
|
Caarg rest; /* the rest-argument */
|
|
char **defs; /* the original strings */
|
|
int ndefs; /* number of ... */
|
|
int lastt; /* last time this was used */
|
|
Caopt *single; /* array of single-letter options */
|
|
char *match; /* -M spec to use */
|
|
int argsactive; /* if arguments are still allowed */
|
|
/* used while parsing a command line */
|
|
char *set; /* set name prefix (<name>-), shared */
|
|
char *sname; /* set name */
|
|
int flags; /* see CDF_* below */
|
|
char *nonarg; /* pattern for non-args (-A argument) */
|
|
};
|
|
|
|
#define CDF_SEP 1
|
|
|
|
/* Description for an option. */
|
|
|
|
struct caopt {
|
|
Caopt next;
|
|
char *name; /* option name */
|
|
char *descr; /* the description */
|
|
char **xor; /* if this, then not ... */
|
|
int type; /* type, CAO_* */
|
|
Caarg args; /* option arguments */
|
|
int active; /* still allowed on command line */
|
|
int num; /* it's the num'th option */
|
|
char *set; /* set name, shared */
|
|
int not; /* don't complete this option (`!...') */
|
|
};
|
|
|
|
#define CAO_NEXT 1
|
|
#define CAO_DIRECT 2
|
|
#define CAO_ODIRECT 3
|
|
#define CAO_EQUAL 4
|
|
#define CAO_OEQUAL 5
|
|
|
|
/* Description for an argument */
|
|
|
|
struct caarg {
|
|
Caarg next;
|
|
char *descr; /* description */
|
|
char **xor; /* if this, then not ... */
|
|
char *action; /* what to do for it */
|
|
int type; /* CAA_* below */
|
|
char *end; /* end-pattern for ::<pat>:... */
|
|
char *opt; /* option name if for an option */
|
|
int num; /* it's the num'th argument */
|
|
int min; /* it's also this argument, using opt. args */
|
|
int direct; /* number was given directly */
|
|
int active; /* still allowed on command line */
|
|
char *set; /* set name, shared */
|
|
};
|
|
|
|
#define CAA_NORMAL 1
|
|
#define CAA_OPT 2
|
|
#define CAA_REST 3
|
|
#define CAA_RARGS 4
|
|
#define CAA_RREST 5
|
|
|
|
/* The cache of parsed descriptons. */
|
|
|
|
#define MAX_CACACHE 8
|
|
static Cadef cadef_cache[MAX_CACACHE];
|
|
|
|
/* Compare two arrays of strings for equality. */
|
|
|
|
static int
|
|
arrcmp(char **a, char **b)
|
|
{
|
|
if (!a && !b)
|
|
return 1;
|
|
else if (!a || !b)
|
|
return 0;
|
|
else {
|
|
while (*a && *b)
|
|
if (strcmp(*a++, *b++))
|
|
return 0;
|
|
|
|
return (!*a && !*b);
|
|
}
|
|
}
|
|
|
|
/* Memory stuff. Obviously. */
|
|
|
|
static void
|
|
freecaargs(Caarg a)
|
|
{
|
|
Caarg n;
|
|
|
|
for (; a; a = n) {
|
|
n = a->next;
|
|
zsfree(a->descr);
|
|
if (a->xor)
|
|
freearray(a->xor);
|
|
zsfree(a->action);
|
|
zsfree(a->end);
|
|
zsfree(a->opt);
|
|
zfree(a, sizeof(*a));
|
|
}
|
|
}
|
|
|
|
static void
|
|
freecadef(Cadef d)
|
|
{
|
|
Cadef s;
|
|
Caopt p, n;
|
|
|
|
while (d) {
|
|
s = d->snext;
|
|
zsfree(d->match);
|
|
zsfree(d->set);
|
|
zsfree(d->sname);
|
|
if (d->defs)
|
|
freearray(d->defs);
|
|
|
|
for (p = d->opts; p; p = n) {
|
|
n = p->next;
|
|
zsfree(p->name);
|
|
zsfree(p->descr);
|
|
if (p->xor)
|
|
freearray(p->xor);
|
|
freecaargs(p->args);
|
|
zfree(p, sizeof(*p));
|
|
}
|
|
freecaargs(d->args);
|
|
freecaargs(d->rest);
|
|
zsfree(d->nonarg);
|
|
if (d->single)
|
|
zfree(d->single, 256 * sizeof(Caopt));
|
|
zfree(d, sizeof(*d));
|
|
d = s;
|
|
}
|
|
}
|
|
|
|
/* Remove backslashes before colons. */
|
|
|
|
static char *
|
|
rembslashcolon(char *s)
|
|
{
|
|
char *p, *r;
|
|
|
|
r = p = s = dupstring(s);
|
|
|
|
while (*s) {
|
|
if (s[0] != '\\' || s[1] != ':')
|
|
*p++ = *s;
|
|
s++;
|
|
}
|
|
*p = '\0';
|
|
|
|
return r;
|
|
}
|
|
|
|
/* Add backslashes before colons. */
|
|
|
|
static char *
|
|
bslashcolon(char *s)
|
|
{
|
|
char *p, *r;
|
|
|
|
r = p = zhalloc((2 * strlen(s)) + 1);
|
|
|
|
while (*s) {
|
|
if (*s == ':')
|
|
*p++ = '\\';
|
|
*p++ = *s++;
|
|
}
|
|
*p = '\0';
|
|
|
|
return r;
|
|
}
|
|
|
|
/* Parse an argument definition. */
|
|
|
|
static Caarg
|
|
parse_caarg(int mult, int type, int num, int opt, char *oname, char **def,
|
|
char *set)
|
|
{
|
|
Caarg ret = (Caarg) zalloc(sizeof(*ret));
|
|
char *p = *def, *d, sav;
|
|
|
|
ret->next = NULL;
|
|
ret->descr = ret->action = ret->end = NULL;
|
|
ret->xor = NULL;
|
|
ret->num = num;
|
|
ret->min = num - opt;
|
|
ret->type = type;
|
|
ret->opt = ztrdup(oname);
|
|
ret->direct = 0;
|
|
ret->set = set;
|
|
|
|
/* Get the description. */
|
|
|
|
for (d = p; *p && *p != ':'; p++)
|
|
if (*p == '\\' && p[1])
|
|
p++;
|
|
sav = *p;
|
|
*p = '\0';
|
|
ret->descr = ztrdup(rembslashcolon(d));
|
|
|
|
/* Get the action if there is one. */
|
|
|
|
if (sav) {
|
|
if (mult) {
|
|
for (d = ++p; *p && *p != ':'; p++)
|
|
if (*p == '\\' && p[1])
|
|
p++;
|
|
sav = *p;
|
|
*p = '\0';
|
|
ret->action = ztrdup(rembslashcolon(d));
|
|
if (sav)
|
|
*p = ':';
|
|
} else
|
|
ret->action = ztrdup(rembslashcolon(p + 1));
|
|
} else
|
|
ret->action = ztrdup("");
|
|
*def = p;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static Cadef
|
|
alloc_cadef(char **args, int single, char *match, char *nonarg, int flags)
|
|
{
|
|
Cadef ret;
|
|
|
|
ret = (Cadef) zalloc(sizeof(*ret));
|
|
ret->next = ret->snext = NULL;
|
|
ret->opts = NULL;
|
|
ret->args = ret->rest = NULL;
|
|
ret->nonarg = ztrdup(nonarg);
|
|
if (args) {
|
|
ret->defs = zarrdup(args);
|
|
ret->ndefs = arrlen(args);
|
|
} else {
|
|
ret->defs = NULL;
|
|
ret->ndefs = 0;
|
|
}
|
|
ret->lastt = time(0);
|
|
ret->set = ret->sname = NULL;
|
|
if (single) {
|
|
ret->single = (Caopt *) zalloc(256 * sizeof(Caopt));
|
|
memset(ret->single, 0, 256 * sizeof(Caopt));
|
|
} else
|
|
ret->single = NULL;
|
|
ret->match = ztrdup(match);
|
|
ret->flags = flags;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
set_cadef_opts(Cadef def)
|
|
{
|
|
Caarg argp;
|
|
int xnum;
|
|
|
|
for (argp = def->args, xnum = 0; argp; argp = argp->next) {
|
|
if (!argp->direct)
|
|
argp->min = argp->num - xnum;
|
|
if (argp->type == CAA_OPT)
|
|
xnum++;
|
|
}
|
|
}
|
|
|
|
/* Parse an array of definitions. */
|
|
|
|
static Cadef
|
|
parse_cadef(char *nam, char **args)
|
|
{
|
|
Cadef all, ret;
|
|
Caopt *optp;
|
|
char **orig_args = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor, **sargs;
|
|
char *adpre, *adsuf, *axor = NULL, *doset = NULL, **setp = NULL;
|
|
char *nonarg = NULL;
|
|
int single = 0, anum = 1, xnum, nopts, ndopts, nodopts, flags = 0;
|
|
int state = 0, not = 0;
|
|
|
|
nopts = ndopts = nodopts = 0;
|
|
|
|
/* First string is the auto-description definition. */
|
|
|
|
for (p = args[0]; *p && (p[0] != '%' || p[1] != 'd'); p++);
|
|
|
|
if (*p) {
|
|
*p = '\0';
|
|
adpre = dupstring(args[0]);
|
|
*p = '%';
|
|
adsuf = dupstring(p + 2);
|
|
} else
|
|
adpre = adsuf = NULL;
|
|
|
|
/* Now get the -s, -A, -S and -M options. */
|
|
|
|
args++;
|
|
while ((p = *args) && *p == '-' && p[1]) {
|
|
for (q = ++p; *q; q++)
|
|
if (*q == 'M' || *q == 'A') {
|
|
q = "";
|
|
break;
|
|
} else if (*q != 's' && *q != 'S')
|
|
break;
|
|
|
|
if (*q)
|
|
break;
|
|
|
|
for (; *p; p++) {
|
|
if (*p == 's')
|
|
single = 1;
|
|
else if (*p == 'S')
|
|
flags |= CDF_SEP;
|
|
else if (*p == 'A') {
|
|
if (p[1]) {
|
|
nonarg = p + 1;
|
|
p = "" - 1;
|
|
} else if (args[1])
|
|
nonarg = *++args;
|
|
else
|
|
break;
|
|
} else if (*p == 'M') {
|
|
if (p[1]) {
|
|
match = p + 1;
|
|
p = "" - 1;
|
|
} else if (args[1])
|
|
match = *++args;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
if (*p)
|
|
break;
|
|
|
|
args++;
|
|
}
|
|
if (*args && !strcmp(*args, ":"))
|
|
args++;
|
|
if (!*args)
|
|
return NULL;
|
|
|
|
if (nonarg)
|
|
tokenize(nonarg = dupstring(nonarg));
|
|
|
|
/* Looks good. Optimistically allocate the cadef structure. */
|
|
|
|
all = ret = alloc_cadef(orig_args, single, match, nonarg, flags);
|
|
optp = &(ret->opts);
|
|
anum = 1;
|
|
|
|
sargs = args;
|
|
|
|
/* Get the definitions. */
|
|
|
|
for (; *args; args++) {
|
|
if (args[0][0] == '-' && !args[0][1] && args[1]) {
|
|
if (!state) {
|
|
char *p;
|
|
int l;
|
|
|
|
if (setp)
|
|
args = setp;
|
|
p = *++args;
|
|
l = strlen(p) - 1;
|
|
if (*p == '(' && p[l] == ')') {
|
|
axor = p = dupstring(p + 1);
|
|
p[l - 1] = '\0';
|
|
} else
|
|
axor = NULL;
|
|
ret->set = doset = tricat(p, "-", "");
|
|
ret->sname = ztrdup(p);
|
|
state = 1;
|
|
} else {
|
|
setp = args;
|
|
state = 0;
|
|
args = sargs - 1;
|
|
doset = NULL;
|
|
ret->nopts = nopts;
|
|
ret->ndopts = ndopts;
|
|
ret->nodopts = nodopts;
|
|
set_cadef_opts(ret);
|
|
ret = ret->snext = alloc_cadef(NULL, single, NULL, nonarg, flags);
|
|
optp = &(ret->opts);
|
|
nopts = ndopts = nodopts = 0;
|
|
anum = 1;
|
|
}
|
|
continue;
|
|
}
|
|
p = dupstring(*args);
|
|
xnum = 0;
|
|
if ((not = (*p == '!')))
|
|
p++;
|
|
if (*p == '(') {
|
|
/* There is a xor list, get it. */
|
|
|
|
LinkList list = newlinklist();
|
|
LinkNode node;
|
|
char **xp, sav;
|
|
|
|
while (*p && *p != ')') {
|
|
for (p++; inblank(*p); p++);
|
|
|
|
if (*p == ')')
|
|
break;
|
|
for (q = p++; *p && *p != ')' && !inblank(*p); p++);
|
|
|
|
if (!*p)
|
|
break;
|
|
|
|
sav = *p;
|
|
*p = '\0';
|
|
addlinknode(list, dupstring(q));
|
|
xnum++;
|
|
*p = sav;
|
|
}
|
|
/* Oops, end-of-string. */
|
|
if (*p != ')') {
|
|
freecadef(all);
|
|
zwarnnam(nam, "invalid argument: %s", *args);
|
|
return NULL;
|
|
}
|
|
if (doset && axor)
|
|
xnum++;
|
|
xor = (char **) zalloc((xnum + 2) * sizeof(char *));
|
|
for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
|
|
*xp = ztrdup((char *) getdata(node));
|
|
if (doset && axor)
|
|
*xp++ = ztrdup(axor);
|
|
xp[0] = xp[1] = NULL;
|
|
|
|
p++;
|
|
} else if (doset && axor) {
|
|
xnum = 1;
|
|
xor = (char **) zalloc(3 * sizeof(char *));
|
|
xor[0] = ztrdup(axor);
|
|
xor[1] = xor[2] = NULL;
|
|
} else
|
|
xor = NULL;
|
|
|
|
if (*p == '-' || *p == '+' ||
|
|
(*p == '*' && (p[1] == '-' || p[1] == '+'))) {
|
|
/* It's an option. */
|
|
Caopt opt;
|
|
Caarg oargs = NULL;
|
|
int multi, otype = CAO_NEXT, again = 0;
|
|
char *name, *descr, c, *againp = NULL;
|
|
|
|
rec:
|
|
|
|
/* Allowed more than once? */
|
|
if ((multi = (*p == '*')))
|
|
p++;
|
|
|
|
if (((p[0] == '-' && p[1] == '+') ||
|
|
(p[0] == '+' && p[1] == '-')) &&
|
|
p[2] && p[2] != ':' && p[2] != '[' &&
|
|
p[2] != '=' && p[2] != '-' && p[2] != '+') {
|
|
/* It's a -+ or +- definition. We just execute the whole
|
|
* stuff twice for such things. */
|
|
againp = dupstring(p);
|
|
name = ++p;
|
|
*p = (again ? '-' : '+');
|
|
again++;
|
|
} else {
|
|
name = p;
|
|
/* If it's a long option skip over the first `-'. */
|
|
if (p[0] == '-' && p[1] == '-')
|
|
p++;
|
|
}
|
|
if (!p[1]) {
|
|
freecadef(all);
|
|
zwarnnam(nam, "invalid argument: %s", *args);
|
|
return NULL;
|
|
}
|
|
|
|
/* Skip over the name. */
|
|
for (p++; *p && *p != ':' && *p != '[' &&
|
|
((*p != '-' && *p != '+') ||
|
|
(p[1] != ':' && p[1] != '[')) &&
|
|
(*p != '=' ||
|
|
(p[1] != ':' && p[1] != '[' && p[1] != '-')); p++)
|
|
if (*p == '\\' && p[1])
|
|
p++;
|
|
|
|
/* The character after the option name specifies the type. */
|
|
c = *p;
|
|
*p = '\0';
|
|
if (c == '-') {
|
|
otype = CAO_DIRECT;
|
|
c = *++p;
|
|
} else if (c == '+') {
|
|
otype = CAO_ODIRECT;
|
|
c = *++p;
|
|
} else if (c == '=') {
|
|
otype = CAO_OEQUAL;
|
|
if ((c = *++p) == '-') {
|
|
otype = CAO_EQUAL;
|
|
c = *++p;
|
|
}
|
|
}
|
|
/* Get the optional description, if any. */
|
|
if (c == '[') {
|
|
for (descr = ++p; *p && *p != ']'; p++)
|
|
if (*p == '\\' && p[1])
|
|
p++;
|
|
|
|
if (!*p) {
|
|
freecadef(all);
|
|
zwarnnam(nam, "invalid option definition: %s", *args);
|
|
return NULL;
|
|
}
|
|
*p++ = '\0';
|
|
c = *p;
|
|
} else
|
|
descr = NULL;
|
|
|
|
if (c && c != ':') {
|
|
freecadef(all);
|
|
zwarnnam(nam, "invalid option definition: %s", *args);
|
|
return NULL;
|
|
}
|
|
/* Add the option name to the xor list if not `*-...'. */
|
|
if (!multi) {
|
|
if (!xor) {
|
|
xor = (char **) zalloc(2 * sizeof(char *));
|
|
xor[0] = xor[1] = NULL;
|
|
}
|
|
zsfree(xor[xnum]);
|
|
xor[xnum] = ztrdup(rembslashcolon(name));
|
|
}
|
|
if (c == ':') {
|
|
/* There's at least one argument. */
|
|
|
|
Caarg *oargp = &oargs;
|
|
int atype, rest, oanum = 1, onum = 0;
|
|
char *end;
|
|
|
|
/* Loop over the arguments. */
|
|
|
|
while (c == ':') {
|
|
rest = 0;
|
|
end = NULL;
|
|
|
|
/* Get the argument type. */
|
|
if (*++p == ':') {
|
|
atype = CAA_OPT;
|
|
p++;
|
|
} else if (*p == '*') {
|
|
if (*++p != ':') {
|
|
char sav;
|
|
|
|
for (end = p++; *p && *p != ':'; p++)
|
|
if (*p == '\\' && p[1])
|
|
p++;
|
|
sav = *p;
|
|
*p = '\0';
|
|
end = dupstring(end);
|
|
tokenize(end);
|
|
*p = sav;
|
|
}
|
|
if (*p != ':') {
|
|
freecadef(all);
|
|
freecaargs(oargs);
|
|
zwarnnam(nam, "invalid option definition: %s",
|
|
*args);
|
|
return NULL;
|
|
}
|
|
if (*++p == ':') {
|
|
if (*++p == ':') {
|
|
atype = CAA_RREST;
|
|
p++;
|
|
} else
|
|
atype = CAA_RARGS;
|
|
} else
|
|
atype = CAA_REST;
|
|
rest = 1;
|
|
} else
|
|
atype = CAA_NORMAL;
|
|
|
|
/* And the definition. */
|
|
|
|
*oargp = parse_caarg(!rest, atype, oanum++, onum,
|
|
name, &p, doset);
|
|
if (atype == CAA_OPT)
|
|
onum++;
|
|
if (end)
|
|
(*oargp)->end = ztrdup(end);
|
|
oargp = &((*oargp)->next);
|
|
if (rest)
|
|
break;
|
|
c = *p;
|
|
}
|
|
}
|
|
/* Store the option definition. */
|
|
|
|
*optp = opt = (Caopt) zalloc(sizeof(*opt));
|
|
optp = &((*optp)->next);
|
|
|
|
opt->next = NULL;
|
|
opt->set = doset;
|
|
opt->name = ztrdup(rembslashcolon(name));
|
|
if (descr)
|
|
opt->descr = ztrdup(descr);
|
|
else if (adpre && oargs && !oargs->next) {
|
|
char *d;
|
|
|
|
for (d = oargs->descr; *d; d++)
|
|
if (!iblank(*d))
|
|
break;
|
|
|
|
if (*d)
|
|
opt->descr = tricat(adpre, oargs->descr, adsuf);
|
|
else
|
|
opt->descr = NULL;
|
|
} else
|
|
opt->descr = NULL;
|
|
opt->xor = (again == 1 && xor ? zarrdup(xor) : xor);
|
|
opt->type = otype;
|
|
opt->args = oargs;
|
|
opt->num = nopts++;
|
|
opt->not = not;
|
|
|
|
if (otype == CAO_DIRECT || otype == CAO_EQUAL)
|
|
ndopts++;
|
|
else if (otype == CAO_ODIRECT || otype == CAO_OEQUAL)
|
|
nodopts++;
|
|
|
|
/* If this is for single-letter option we also store a
|
|
* pointer for the definition in the array for fast lookup. */
|
|
|
|
if (single && name[1] && !name[2])
|
|
ret->single[STOUC(name[1])] = opt;
|
|
|
|
if (again == 1) {
|
|
/* Do it all again for `*-...'. */
|
|
p = againp;
|
|
goto rec;
|
|
}
|
|
} else if (*p == '*') {
|
|
/* It's a rest-argument definition. */
|
|
|
|
int type = CAA_REST;
|
|
|
|
if (not)
|
|
continue;
|
|
|
|
if (*++p != ':') {
|
|
freecadef(all);
|
|
zwarnnam(nam, "invalid rest argument definition: %s", *args);
|
|
return NULL;
|
|
}
|
|
if (ret->rest) {
|
|
freecadef(all);
|
|
zwarnnam(nam, "doubled rest argument definition: %s", *args);
|
|
return NULL;
|
|
}
|
|
if (*++p == ':') {
|
|
if (*++p == ':') {
|
|
type = CAA_RREST;
|
|
p++;
|
|
} else
|
|
type = CAA_RARGS;
|
|
}
|
|
ret->rest = parse_caarg(0, type, -1, 0, NULL, &p, doset);
|
|
ret->rest->xor = xor;
|
|
} else {
|
|
/* It's a normal argument definition. */
|
|
|
|
int type = CAA_NORMAL, direct;
|
|
Caarg arg, tmp, pre;
|
|
|
|
if (not)
|
|
continue;
|
|
|
|
if ((direct = idigit(*p))) {
|
|
/* Argment number is given. */
|
|
int num = 0;
|
|
|
|
while (*p && idigit(*p))
|
|
num = (num * 10) + (((int) *p++) - '0');
|
|
|
|
anum = num + 1;
|
|
} else
|
|
/* Default number. */
|
|
anum++;
|
|
|
|
if (*p != ':') {
|
|
freecadef(all);
|
|
zwarnnam(nam, "invalid argument: %s", *args);
|
|
if (xor)
|
|
free(xor);
|
|
return NULL;
|
|
}
|
|
if (*++p == ':') {
|
|
/* Optional argument. */
|
|
type = CAA_OPT;
|
|
p++;
|
|
}
|
|
arg = parse_caarg(0, type, anum - 1, 0, NULL, &p, doset);
|
|
arg->xor = xor;
|
|
arg->direct = direct;
|
|
|
|
/* Sort the new definition into the existing list. */
|
|
|
|
for (tmp = ret->args, pre = NULL;
|
|
tmp && tmp->num < anum - 1;
|
|
pre = tmp, tmp = tmp->next);
|
|
|
|
if (tmp && tmp->num == anum - 1) {
|
|
freecadef(all);
|
|
freecaargs(arg);
|
|
zwarnnam(nam, "doubled argument definition: %s", *args);
|
|
return NULL;
|
|
}
|
|
arg->next = tmp;
|
|
if (pre)
|
|
pre->next = arg;
|
|
else
|
|
ret->args = arg;
|
|
}
|
|
}
|
|
ret->nopts = nopts;
|
|
ret->ndopts = ndopts;
|
|
ret->nodopts = nodopts;
|
|
set_cadef_opts(ret);
|
|
|
|
return all;
|
|
}
|
|
|
|
/* Given an array of definitions, return the cadef for it. From the cache
|
|
* are newly built. */
|
|
|
|
static Cadef
|
|
get_cadef(char *nam, char **args)
|
|
{
|
|
Cadef *p, *min, new;
|
|
int i, na = arrlen(args);
|
|
|
|
for (i = MAX_CACACHE, p = cadef_cache, min = NULL; i && *p; p++, i--)
|
|
if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
|
|
(*p)->lastt = time(0);
|
|
|
|
return *p;
|
|
} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
|
|
min = p;
|
|
if (i > 0)
|
|
min = p;
|
|
if ((new = parse_cadef(nam, args))) {
|
|
freecadef(*min);
|
|
*min = new;
|
|
}
|
|
return new;
|
|
}
|
|
|
|
/*
|
|
* Get the option used in a word from the line, if any.
|
|
*
|
|
* "d" is a complete set of argument/option definitions to scan.
|
|
* "line" is the word we are scanning.
|
|
* "full" indicates that the option must match a full word; otherwise
|
|
* we look for "=" arguments or prefixes.
|
|
* *"end" is set to point to the end of the option, in some cases
|
|
* leaving an option argument after it.
|
|
*/
|
|
|
|
static Caopt
|
|
ca_get_opt(Cadef d, char *line, int full, char **end)
|
|
{
|
|
Caopt p;
|
|
|
|
/* The full string may be an option. */
|
|
|
|
for (p = d->opts; p; p = p->next)
|
|
if (p->active && !strcmp(p->name, line)) {
|
|
if (end)
|
|
*end = line + strlen(line);
|
|
|
|
return p;
|
|
}
|
|
|
|
if (!full) {
|
|
/* The string from the line probably only begins with an option. */
|
|
for (p = d->opts; p; p = p->next)
|
|
if (p->active && ((!p->args || p->type == CAO_NEXT) ?
|
|
!strcmp(p->name, line) : strpfx(p->name, line))) {
|
|
if (end) {
|
|
/* Return a pointer to the end of the option. */
|
|
int l = strlen(p->name);
|
|
|
|
if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
|
|
line[l] == '=')
|
|
l++;
|
|
|
|
*end = line + l;
|
|
}
|
|
return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Same as above, only for single-letter-style. */
|
|
|
|
static Caopt
|
|
ca_get_sopt(Cadef d, char *line, char **end, LinkList *lp)
|
|
{
|
|
Caopt p, pp = NULL;
|
|
char pre = *line++;
|
|
LinkList l = NULL;
|
|
|
|
*lp = NULL;
|
|
for (p = NULL; *line; line++) {
|
|
if ((p = d->single[STOUC(*line)]) && p->active &&
|
|
p->args && p->name[0] == pre) {
|
|
if (p->type == CAO_NEXT) {
|
|
if (!l)
|
|
*lp = l = newlinklist();
|
|
addlinknode(l, p);
|
|
} else {
|
|
if (end) {
|
|
line++;
|
|
if ((p->type == CAO_OEQUAL || p->type == CAO_EQUAL) &&
|
|
*line == '=')
|
|
line++;
|
|
*end = line;
|
|
}
|
|
pp = p;
|
|
break;
|
|
}
|
|
} else if (!p || (p && !p->active))
|
|
return NULL;
|
|
pp = (p->name[0] == pre ? p : NULL);
|
|
p = NULL;
|
|
}
|
|
if (pp && end)
|
|
*end = line;
|
|
return pp;
|
|
}
|
|
|
|
/* Return the n'th argument definition. */
|
|
|
|
static Caarg
|
|
ca_get_arg(Cadef d, int n)
|
|
{
|
|
if (d->argsactive) {
|
|
Caarg a = d->args;
|
|
|
|
while (a && (!a->active || n < a->min || n > a->num)) {
|
|
if (!a->active)
|
|
n++;
|
|
a = a->next;
|
|
}
|
|
if (a && a->min <= n && a->num >= n && a->active)
|
|
return a;
|
|
|
|
return (d->rest && d->rest->active ? d->rest : NULL);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Use a xor list, marking options as inactive. */
|
|
|
|
static LinkList ca_xor;
|
|
|
|
static int
|
|
ca_inactive(Cadef d, char **xor, int cur, int opts, char *optname)
|
|
{
|
|
if ((xor || opts) && cur <= compcurrent) {
|
|
Caopt opt;
|
|
char *x;
|
|
int sl = (d->set ? (int)strlen(d->set) : -1), set = 0;
|
|
|
|
for (; (x = (opts ? "-" : *xor)); xor++) {
|
|
if (optname && optname[0] == x[0] && strcmp(optname, x))
|
|
continue;
|
|
if (ca_xor)
|
|
addlinknode(ca_xor, x);
|
|
set = 0;
|
|
if (sl > 0) {
|
|
if (strpfx(d->set, x)) {
|
|
x += sl;
|
|
set = 1;
|
|
} else if (!strncmp(d->set, x, sl - 1)) {
|
|
Caopt p;
|
|
|
|
for (p = d->opts; p; p = p->next)
|
|
if (p->set)
|
|
p->active = 0;
|
|
|
|
x = ":";
|
|
set = 1;
|
|
}
|
|
}
|
|
if (x[0] == ':' && !x[1]) {
|
|
if (set) {
|
|
Caarg a;
|
|
|
|
for (a = d->args; a; a = a->next)
|
|
if (a->set)
|
|
a->active = 0;
|
|
if (d->rest && (!set || d->rest->set))
|
|
d->rest->active = 0;
|
|
} else
|
|
d->argsactive = 0;
|
|
} else if (x[0] == '-' && !x[1]) {
|
|
Caopt p;
|
|
|
|
for (p = d->opts; p; p = p->next)
|
|
if (!set || p->set)
|
|
p->active = 0;
|
|
} else if (x[0] == '*' && !x[1]) {
|
|
if (d->rest && (!set || d->rest->set))
|
|
d->rest->active = 0;
|
|
} else if (idigit(x[0])) {
|
|
int n = atoi(x);
|
|
Caarg a = d->args;
|
|
|
|
while (a && a->num < n)
|
|
a = a->next;
|
|
|
|
if (a && a->num == n && (!set || a->set))
|
|
a->active = 0;
|
|
} else if ((opt = ca_get_opt(d, x, 1, NULL)) && (!set || opt->set))
|
|
opt->active = 0;
|
|
|
|
if (opts)
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* State when parsing a command line. */
|
|
|
|
typedef struct castate *Castate;
|
|
|
|
/*
|
|
* **** DOCUMENT ME ****
|
|
*
|
|
* This structure and its use are a nightmare.
|
|
*/
|
|
|
|
struct castate {
|
|
Castate snext;
|
|
Cadef d;
|
|
int nopts;
|
|
Caarg def, ddef;
|
|
Caopt curopt, dopt;
|
|
int opt, arg, argbeg, optbeg, nargbeg, restbeg, curpos, argend;
|
|
int inopt, inrest, inarg, nth, doff, singles, oopt, actopts;
|
|
LinkList args;
|
|
LinkList *oargs;
|
|
};
|
|
|
|
static struct castate ca_laststate;
|
|
static int ca_parsed = 0, ca_alloced = 0;
|
|
|
|
static void
|
|
freecastate(Castate s)
|
|
{
|
|
int i;
|
|
LinkList *p;
|
|
|
|
freelinklist(s->args, freestr);
|
|
for (i = s->nopts, p = s->oargs; i--; p++)
|
|
if (*p)
|
|
freelinklist(*p, freestr);
|
|
zfree(s->oargs, s->d->nopts * sizeof(LinkList));
|
|
}
|
|
|
|
/* Return a copy of an option's argument, ignoring possible quoting
|
|
* in the option name. */
|
|
|
|
static char *
|
|
ca_opt_arg(Caopt opt, char *line)
|
|
{
|
|
char *o = opt->name;
|
|
|
|
while (1) {
|
|
if (*o == '\\')
|
|
o++;
|
|
if (*line == '\\' || *line == '\'' || *line == '"')
|
|
line++;
|
|
if (!*o || *o != *line)
|
|
break;
|
|
o++;
|
|
line++;
|
|
}
|
|
if (*line && (opt->type == CAO_EQUAL || opt->type == CAO_OEQUAL)) {
|
|
if (*line == '\\')
|
|
line++;
|
|
if (*line == '=')
|
|
line++;
|
|
}
|
|
return ztrdup(line);
|
|
}
|
|
|
|
/* Parse a command line. */
|
|
|
|
static int
|
|
ca_parse_line(Cadef d, int multi, int first)
|
|
{
|
|
Caarg adef, ddef;
|
|
Caopt ptr, wasopt = NULL, dopt;
|
|
struct castate state;
|
|
char *line, *oline, *pe, **argxor = NULL;
|
|
int cur, doff, argend, arglast;
|
|
Patprog endpat = NULL, napat = NULL;
|
|
LinkList sopts = NULL;
|
|
#if 0
|
|
int ne;
|
|
#endif
|
|
|
|
/* Free old state. */
|
|
|
|
if (first && ca_alloced) {
|
|
Castate s = &ca_laststate, ss;
|
|
|
|
while (s) {
|
|
ss = s->snext;
|
|
freecastate(s);
|
|
s = ss;
|
|
}
|
|
}
|
|
/* Mark everything as active. */
|
|
|
|
for (ptr = d->opts; ptr; ptr = ptr->next)
|
|
ptr->active = 1;
|
|
d->argsactive = 1;
|
|
if (d->rest)
|
|
d->rest->active = 1;
|
|
for (adef = d->args; adef; adef = adef->next)
|
|
adef->active = 1;
|
|
|
|
/* Default values for the state. */
|
|
|
|
state.snext = NULL;
|
|
state.d = d;
|
|
state.nopts = d->nopts;
|
|
state.def = state.ddef = NULL;
|
|
state.curopt = state.dopt = NULL;
|
|
state.argbeg = state.optbeg = state.nargbeg = state.restbeg = state.actopts =
|
|
state.nth = state.inopt = state.inarg = state.opt = state.arg = 1;
|
|
state.argend = argend = arrlen(compwords) - 1;
|
|
state.inrest = state.doff = state.singles = state.oopt = 0;
|
|
state.curpos = compcurrent;
|
|
state.args = znewlinklist();
|
|
state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
|
|
memset(state.oargs, 0, d->nopts * sizeof(LinkList));
|
|
|
|
ca_alloced = 1;
|
|
|
|
memcpy(&ca_laststate, &state, sizeof(state));
|
|
|
|
if (!compwords[1]) {
|
|
ca_laststate.opt = ca_laststate.arg = 0;
|
|
|
|
goto end;
|
|
}
|
|
if (d->nonarg)
|
|
napat = patcompile(d->nonarg, 0, NULL);
|
|
|
|
/* Loop over the words from the line. */
|
|
|
|
for (line = compwords[1], cur = 2, state.curopt = NULL, state.def = NULL;
|
|
line; line = compwords[cur++]) {
|
|
ddef = adef = NULL;
|
|
dopt = NULL;
|
|
doff = state.singles = arglast = 0;
|
|
|
|
oline = line;
|
|
#if 0
|
|
/*
|
|
* remove quotes.
|
|
* This is commented out: it doesn't allow you to discriminate
|
|
* between command line values that can be expanded and those
|
|
* that can't, and in some cases this generates inconsistency;
|
|
* for example, ~/foo\[bar unqotes to ~/foo[bar which doesn't
|
|
* work either way---it's wrong if the ~ is quoted, and
|
|
* wrong if the [ isn't quoted.. So it's now up to the caller to
|
|
* unquote.
|
|
*/
|
|
line = dupstring(line);
|
|
ne = noerrs;
|
|
noerrs = 2;
|
|
parse_subst_string(line);
|
|
noerrs = ne;
|
|
#endif
|
|
remnulargs(line);
|
|
untokenize(line);
|
|
|
|
if (ca_inactive(d, argxor, cur, 0, NULL) ||
|
|
((d->flags & CDF_SEP) && cur != compcurrent && !strcmp(line, "--"))) {
|
|
if (ca_inactive(d, NULL, cur, 1, NULL))
|
|
return 1;
|
|
continue;
|
|
}
|
|
/* We've got a definition for an argument, skip to the next. */
|
|
|
|
if (state.def) {
|
|
state.arg = 0;
|
|
if (state.curopt)
|
|
zaddlinknode(state.oargs[state.curopt->num], ztrdup(oline));
|
|
|
|
if ((state.opt = (state.def->type == CAA_OPT)) && state.def->opt)
|
|
state.oopt++;
|
|
|
|
if (state.def->type == CAA_REST || state.def->type == CAA_RARGS ||
|
|
state.def->type == CAA_RREST) {
|
|
if (state.def->end && pattry(endpat, line)) {
|
|
state.def = NULL;
|
|
state.curopt = NULL;
|
|
state.opt = state.arg = 1;
|
|
state.argend = ca_laststate.argend = cur - 1;
|
|
goto cont;
|
|
}
|
|
} else if ((state.def = state.def->next)) {
|
|
state.argbeg = cur;
|
|
state.argend = argend;
|
|
} else if (sopts && nonempty(sopts)) {
|
|
state.curopt = (Caopt) uremnode(sopts, firstnode(sopts));
|
|
state.def = state.curopt->args;
|
|
state.opt = 0;
|
|
state.argbeg = state.optbeg = state.inopt = cur;
|
|
state.argend = argend;
|
|
doff = state.doff = 0;
|
|
state.singles = 1;
|
|
if (!state.oargs[state.curopt->num])
|
|
state.oargs[state.curopt->num] = znewlinklist();
|
|
goto cont;
|
|
} else {
|
|
state.curopt = NULL;
|
|
state.opt = 1;
|
|
}
|
|
} else {
|
|
state.opt = state.arg = 1;
|
|
state.curopt = NULL;
|
|
}
|
|
if (state.opt)
|
|
state.opt = (line[0] ? (line[1] ? 2 : 1) : 0);
|
|
|
|
pe = NULL;
|
|
|
|
wasopt = NULL;
|
|
|
|
/* See if it's an option. */
|
|
|
|
if (state.opt == 2 && (state.curopt = ca_get_opt(d, line, 0, &pe)) &&
|
|
(state.curopt->type == CAO_OEQUAL ?
|
|
(compwords[cur] || pe[-1] == '=') :
|
|
(state.curopt->type == CAO_EQUAL ?
|
|
(pe[-1] == '=' || !pe[0]) : 1))) {
|
|
|
|
if ((ddef = state.def = ((state.curopt->type != CAO_EQUAL ||
|
|
pe[-1] == '=') ?
|
|
state.curopt->args : NULL)))
|
|
dopt = state.curopt;
|
|
|
|
doff = pe - line;
|
|
state.optbeg = state.argbeg = state.inopt = cur;
|
|
state.argend = argend;
|
|
state.singles = (d->single && (!pe || !*pe) &&
|
|
state.curopt->name[1] && !state.curopt->name[2]);
|
|
|
|
if (!state.oargs[state.curopt->num])
|
|
state.oargs[state.curopt->num] = znewlinklist();
|
|
|
|
if (ca_inactive(d, state.curopt->xor, cur, 0,
|
|
(cur == compcurrent ? state.curopt->name : NULL)))
|
|
return 1;
|
|
|
|
/* Collect the argument strings. Maybe. */
|
|
|
|
if (state.def &&
|
|
(state.curopt->type == CAO_DIRECT ||
|
|
state.curopt->type == CAO_EQUAL ||
|
|
(state.curopt->type == CAO_ODIRECT && pe[0]) ||
|
|
(state.curopt->type == CAO_OEQUAL &&
|
|
(pe[0] || pe[-1] == '=')))) {
|
|
if (state.def->type != CAA_REST &&
|
|
state.def->type != CAA_RARGS &&
|
|
state.def->type != CAA_RREST)
|
|
state.def = state.def->next;
|
|
|
|
zaddlinknode(state.oargs[state.curopt->num],
|
|
ca_opt_arg(state.curopt, oline));
|
|
}
|
|
if (state.def)
|
|
state.opt = 0;
|
|
else {
|
|
if (!d->single || (state.curopt->name[1] && state.curopt->name[2]))
|
|
wasopt = state.curopt;
|
|
state.curopt = NULL;
|
|
}
|
|
} else if (state.opt == 2 && d->single &&
|
|
((state.curopt = ca_get_sopt(d, line, &pe, &sopts)) ||
|
|
(cur != compcurrent && sopts && nonempty(sopts)))) {
|
|
/* Or maybe it's a single-letter option? */
|
|
|
|
char *p;
|
|
Caopt tmpopt;
|
|
|
|
if (cur != compcurrent && sopts && nonempty(sopts))
|
|
state.curopt = (Caopt) uremnode(sopts, firstnode(sopts));
|
|
|
|
if (!state.oargs[state.curopt->num])
|
|
state.oargs[state.curopt->num] = znewlinklist();
|
|
|
|
state.def = state.curopt->args;
|
|
ddef = (state.curopt->type == CAO_NEXT && cur == compcurrent ?
|
|
NULL : state.def);
|
|
dopt = state.curopt;
|
|
doff = pe - line;
|
|
state.optbeg = state.argbeg = state.inopt = cur;
|
|
state.argend = argend;
|
|
state.singles = (!pe || !*pe);
|
|
|
|
for (p = line + 1; p < pe; p++) {
|
|
if ((tmpopt = d->single[STOUC(*p)])) {
|
|
if (!state.oargs[tmpopt->num])
|
|
state.oargs[tmpopt->num] = znewlinklist();
|
|
|
|
if (ca_inactive(d, tmpopt->xor, cur, 0,
|
|
(cur == compcurrent ? tmpopt->name : NULL)))
|
|
return 1;
|
|
}
|
|
}
|
|
if (state.def &&
|
|
(state.curopt->type == CAO_DIRECT ||
|
|
state.curopt->type == CAO_EQUAL ||
|
|
(state.curopt->type == CAO_ODIRECT && pe[0]) ||
|
|
(state.curopt->type == CAO_OEQUAL &&
|
|
(pe[0] || pe[-1] == '=')))) {
|
|
if (state.def->type != CAA_REST &&
|
|
state.def->type != CAA_RARGS &&
|
|
state.def->type != CAA_RREST)
|
|
state.def = state.def->next;
|
|
|
|
zaddlinknode(state.oargs[state.curopt->num],
|
|
ca_opt_arg(state.curopt, line));
|
|
}
|
|
if (state.def)
|
|
state.opt = 0;
|
|
else
|
|
state.curopt = NULL;
|
|
} else if (multi && (*line == '-' || *line == '+') && cur != compcurrent
|
|
#if 0
|
|
/**** Ouch. Using this will disable the mutual exclusion
|
|
of different sets. Not using it will make the -A
|
|
pattern be effectively ignored with multiple sets. */
|
|
&& (!napat || !pattry(napat, line))
|
|
#endif
|
|
)
|
|
return 1;
|
|
else if (state.arg && (!napat || !pattry(napat, line))) {
|
|
/* Otherwise it's a normal argument. */
|
|
if (napat && ca_inactive(d, NULL, cur + 1, 1, NULL))
|
|
return 1;
|
|
|
|
arglast = 1;
|
|
if (state.inopt) {
|
|
state.inopt = 0;
|
|
state.nargbeg = cur - 1;
|
|
state.argend = argend;
|
|
}
|
|
if (!d->args && !d->rest && *line && *line != '-' && *line != '+') {
|
|
if (!multi && cur > compcurrent)
|
|
break;
|
|
return 1;
|
|
}
|
|
if ((adef = state.def = ca_get_arg(d, state.nth)) &&
|
|
(state.def->type == CAA_RREST ||
|
|
state.def->type == CAA_RARGS)) {
|
|
|
|
/* Bart 2009/11/17:
|
|
* We've reached the "rest" definition. If at this point
|
|
* we already found another definition that describes the
|
|
* current word, use that instead. If not, prep for the
|
|
* "narrowing" of scope to only the remaining words.
|
|
*
|
|
* We can't test ca_laststate.def in the loop conditions
|
|
* at the top because this same loop also handles the
|
|
* ':*PATTERN:MESSAGE:ACTION' form for multiple arguments
|
|
* after an option, which may need to continue scanning.
|
|
* There might be an earlier point at which this test can
|
|
* be made but tracking it down is not worth the effort.
|
|
*/
|
|
if (ca_laststate.def)
|
|
break;
|
|
|
|
state.inrest = 0;
|
|
state.opt = (cur == state.nargbeg + 1 &&
|
|
(!multi || !*line ||
|
|
*line == '-' || *line == '+'));
|
|
state.optbeg = state.nargbeg;
|
|
state.argbeg = cur - 1;
|
|
state.argend = argend;
|
|
|
|
for (; line; line = compwords[cur++])
|
|
zaddlinknode(state.args, ztrdup(line));
|
|
|
|
memcpy(&ca_laststate, &state, sizeof(state));
|
|
ca_laststate.ddef = NULL;
|
|
ca_laststate.dopt = NULL;
|
|
ca_laststate.doff = 0;
|
|
break;
|
|
}
|
|
zaddlinknode(state.args, ztrdup(line));
|
|
if (adef)
|
|
state.oopt = adef->num - state.nth;
|
|
|
|
if (state.def)
|
|
argxor = state.def->xor;
|
|
|
|
if (state.def && state.def->type != CAA_NORMAL &&
|
|
state.def->type != CAA_OPT && state.inarg) {
|
|
state.restbeg = cur;
|
|
state.inarg = 0;
|
|
} else if (!state.def || state.def->type == CAA_NORMAL ||
|
|
state.def->type == CAA_OPT)
|
|
state.inarg = 1;
|
|
state.nth++;
|
|
state.def = NULL;
|
|
}
|
|
/* Do the end-pattern test if needed. */
|
|
|
|
if (state.def && state.curopt &&
|
|
(state.def->type == CAA_RREST || state.def->type == CAA_RARGS)) {
|
|
if (state.def->end)
|
|
endpat = patcompile(state.def->end, 0, NULL);
|
|
else {
|
|
LinkList l = state.oargs[state.curopt->num];
|
|
|
|
if (cur < compcurrent)
|
|
memcpy(&ca_laststate, &state, sizeof(state));
|
|
|
|
for (; line; line = compwords[cur++])
|
|
zaddlinknode(l, ztrdup(line));
|
|
|
|
ca_laststate.ddef = NULL;
|
|
ca_laststate.dopt = NULL;
|
|
ca_laststate.doff = 0;
|
|
break;
|
|
}
|
|
} else if (state.def && state.def->end)
|
|
endpat = patcompile(state.def->end, 0, NULL);
|
|
|
|
/* Copy the state into the global one. */
|
|
|
|
cont:
|
|
|
|
if (cur + 1 == compcurrent) {
|
|
memcpy(&ca_laststate, &state, sizeof(state));
|
|
ca_laststate.ddef = NULL;
|
|
ca_laststate.dopt = NULL;
|
|
ca_laststate.doff = 0;
|
|
} else if (cur == compcurrent && !ca_laststate.def) {
|
|
if ((ca_laststate.def = ddef)) {
|
|
ca_laststate.singles = state.singles;
|
|
if (state.curopt && state.curopt->type == CAO_NEXT) {
|
|
ca_laststate.ddef = ddef;
|
|
ca_laststate.dopt = dopt;
|
|
ca_laststate.def = NULL;
|
|
ca_laststate.opt = 1;
|
|
state.curopt->active = 1;
|
|
} else {
|
|
ca_laststate.doff = doff;
|
|
ca_laststate.opt = 0;
|
|
}
|
|
} else {
|
|
ca_laststate.def = adef;
|
|
ca_laststate.opt = (!arglast || !multi || !*line ||
|
|
*line == '-' || *line == '+');
|
|
ca_laststate.ddef = NULL;
|
|
ca_laststate.dopt = NULL;
|
|
ca_laststate.optbeg = state.nargbeg;
|
|
ca_laststate.argbeg = state.restbeg;
|
|
ca_laststate.argend = state.argend;
|
|
ca_laststate.singles = state.singles;
|
|
ca_laststate.oopt = state.oopt;
|
|
if (wasopt)
|
|
wasopt->active = 1;
|
|
}
|
|
}
|
|
}
|
|
end:
|
|
|
|
ca_laststate.actopts = 0;
|
|
for (ptr = d->opts; ptr; ptr = ptr->next)
|
|
if (ptr->active)
|
|
ca_laststate.actopts++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Build a colon-list from a list. */
|
|
|
|
static char *
|
|
ca_colonlist(LinkList l)
|
|
{
|
|
if (l) {
|
|
LinkNode n;
|
|
int len = 0;
|
|
char *p, *ret, *q;
|
|
|
|
for (n = firstnode(l); n; incnode(n)) {
|
|
len++;
|
|
for (p = (char *) getdata(n); *p; p++)
|
|
len += (*p == ':' ? 2 : 1);
|
|
}
|
|
ret = q = (char *) zalloc(len);
|
|
|
|
for (n = firstnode(l); n;) {
|
|
for (p = (char *) getdata(n); *p; p++) {
|
|
if (*p == ':')
|
|
*q++ = '\\';
|
|
*q++ = *p;
|
|
}
|
|
incnode(n);
|
|
if (n)
|
|
*q++ = ':';
|
|
}
|
|
*q = '\0';
|
|
|
|
return ret;
|
|
} else
|
|
return ztrdup("");
|
|
}
|
|
|
|
/*
|
|
* This function adds the current set of descriptions, actions,
|
|
* and subcontext descriptions to the given linked list for passing
|
|
* up in comparguments -D and comparguments -L. opt is the
|
|
* option string (may be NULL if this isn't an option argument) and arg the
|
|
* argument structure (either an option argument or a normal argument
|
|
* as determined by arg->type).
|
|
*/
|
|
|
|
static void
|
|
ca_set_data(LinkList descr, LinkList act, LinkList subc,
|
|
char *opt, Caarg arg, Caopt optdef, int single)
|
|
{
|
|
LinkNode dnode, anode;
|
|
char nbuf[40], *buf;
|
|
int restr = 0, onum, miss = 0, rest, oopt = 1, lopt = 0, addopt;
|
|
|
|
rec:
|
|
|
|
addopt = (opt ? 0 : ca_laststate.oopt);
|
|
|
|
for (; arg && (opt || (arg->num < 0 ||
|
|
(arg->min <= ca_laststate.nth + addopt &&
|
|
arg->num >= ca_laststate.nth)));) {
|
|
lopt = (arg->type == CAA_OPT);
|
|
if (!opt && !lopt && oopt > 0)
|
|
oopt = 0;
|
|
|
|
for (dnode = firstnode(descr), anode = firstnode(act);
|
|
dnode; incnode(dnode), incnode(anode))
|
|
if (!strcmp((char *) getdata(dnode), arg->descr) &&
|
|
!strcmp((char *) getdata(anode), arg->action))
|
|
break;
|
|
|
|
if (!dnode) {
|
|
addlinknode(descr, arg->descr);
|
|
addlinknode(act, arg->action);
|
|
|
|
if (!restr) {
|
|
if ((restr = (arg->type == CAA_RARGS)))
|
|
restrict_range(ca_laststate.optbeg, ca_laststate.argend);
|
|
else if ((restr = (arg->type == CAA_RREST)))
|
|
restrict_range(ca_laststate.argbeg, ca_laststate.argend);
|
|
}
|
|
if (arg->opt) {
|
|
buf = (char *) zhalloc((arg->set ? strlen(arg->set) : 0) +
|
|
strlen(arg->opt) + 40);
|
|
if (arg->num > 0 && arg->type < CAA_REST)
|
|
sprintf(buf, "%soption%s-%d",
|
|
(arg->set ? arg->set : ""), arg->opt, arg->num);
|
|
else
|
|
sprintf(buf, "%soption%s-rest",
|
|
(arg->set ? arg->set : ""), arg->opt);
|
|
} else if (arg->num > 0) {
|
|
sprintf(nbuf, "argument-%d", arg->num);
|
|
buf = (arg->set ? dyncat(arg->set, nbuf) : dupstring(nbuf));
|
|
} else
|
|
buf = (arg->set ? dyncat(arg->set, "argument-rest") :
|
|
dupstring("argument-rest"));
|
|
|
|
addlinknode(subc, buf);
|
|
}
|
|
/*
|
|
* If this is an argument to an option, and the option definition says
|
|
* the argument to the option is required and in the following
|
|
* (i.e. this) word, then it must match what we've just told it to
|
|
* match---don't try to match normal arguments.
|
|
*
|
|
* This test may be too stringent for what we need, or it
|
|
* may be too loose; I've simply tweaked it until it gets
|
|
* the case above right.
|
|
*/
|
|
if (arg->type == CAA_NORMAL &&
|
|
opt && optdef && optdef->type == CAO_NEXT)
|
|
return;
|
|
if (single)
|
|
break;
|
|
|
|
if (!opt) {
|
|
if (arg->num >= 0 && !arg->next && miss)
|
|
arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
|
|
ca_laststate.d->rest : NULL);
|
|
else {
|
|
onum = arg->num;
|
|
rest = (onum != arg->min && onum == ca_laststate.nth);
|
|
if ((arg = arg->next)) {
|
|
if (arg->num != onum + 1)
|
|
miss = 1;
|
|
} else if (rest || (oopt > 0 && !opt)) {
|
|
arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
|
|
ca_laststate.d->rest : NULL);
|
|
oopt = -1;
|
|
}
|
|
}
|
|
} else {
|
|
if (!lopt)
|
|
break;
|
|
arg = arg->next;
|
|
}
|
|
}
|
|
if (!single && opt && (lopt || ca_laststate.oopt)) {
|
|
opt = NULL;
|
|
arg = ca_get_arg(ca_laststate.d, ca_laststate.nth);
|
|
|
|
goto rec;
|
|
}
|
|
if (!opt && oopt > 0) {
|
|
oopt = -1;
|
|
arg = (ca_laststate.d->rest && ca_laststate.d->rest->active ?
|
|
ca_laststate.d->rest : NULL);
|
|
|
|
goto rec;
|
|
}
|
|
}
|
|
|
|
static int
|
|
bin_comparguments(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
int min, max, n;
|
|
Castate lstate = &ca_laststate;
|
|
|
|
if (incompfunc != 1) {
|
|
zwarnnam(nam, "can only be called from completion function");
|
|
return 1;
|
|
}
|
|
if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
|
|
zwarnnam(nam, "invalid argument: %s", args[0]);
|
|
return 1;
|
|
}
|
|
if (args[0][1] != 'i' && args[0][1] != 'I' && !ca_parsed) {
|
|
zwarnnam(nam, "no parsed state");
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'i': min = 2; max = -1; break;
|
|
case 'D': min = 3; max = 3; break;
|
|
case 'O': min = 4; max = 4; break;
|
|
case 'L': min = 3; max = 4; break;
|
|
case 's': min = 1; max = 1; break;
|
|
case 'M': min = 1; max = 1; break;
|
|
case 'a': min = 0; max = 0; break;
|
|
case 'W': min = 2; max = 2; break;
|
|
case 'n': min = 1; max = 1; break;
|
|
default:
|
|
zwarnnam(nam, "invalid option: %s", args[0]);
|
|
return 1;
|
|
}
|
|
n = arrlen(args) - 1;
|
|
if (n < min) {
|
|
zwarnnam(nam, "not enough arguments");
|
|
return 1;
|
|
} else if (max >= 0 && n > max) {
|
|
zwarnnam(nam, "too many arguments");
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'i':
|
|
/* This initialises the internal data structures. Arguments are the
|
|
* auto-description string, the optional -s, -S, -A and -M options
|
|
* given to _arguments and the specs. */
|
|
if (compcurrent > 1 && compwords[0]) {
|
|
Cadef def;
|
|
int cap = ca_parsed, multi, first = 1, use, ret = 0;
|
|
LinkList cax = ca_xor, nx;
|
|
LinkNode node;
|
|
Castate states = NULL, sp;
|
|
char *xor[2];
|
|
|
|
ca_parsed = 0;
|
|
xor[1] = NULL;
|
|
|
|
if (!(def = get_cadef(nam, args + 1)))
|
|
return 1;
|
|
|
|
multi = !!def->snext;
|
|
ca_parsed = cap;
|
|
ca_xor = (multi ? newlinklist() : NULL);
|
|
|
|
while (def) {
|
|
use = !ca_parse_line(def, multi, first);
|
|
nx = ca_xor;
|
|
ca_xor = NULL;
|
|
while ((def = def->snext)) {
|
|
if (nx) {
|
|
for (node = firstnode(nx); node; incnode(node)) {
|
|
xor[0] = (char *) getdata(node);
|
|
if (!strcmp(xor[0], def->sname) ||
|
|
ca_inactive(def, xor, compcurrent, 0, NULL))
|
|
break;
|
|
}
|
|
if (!node)
|
|
break;
|
|
}
|
|
}
|
|
ca_xor = nx;
|
|
if (use && def) {
|
|
sp = (Castate) zalloc(sizeof(*sp));
|
|
memcpy(sp, &ca_laststate, sizeof(*sp));
|
|
sp->snext = states;
|
|
states = sp;
|
|
} else if (!use && !def) {
|
|
if (states) {
|
|
freecastate(&ca_laststate);
|
|
memcpy(&ca_laststate, states, sizeof(*sp));
|
|
sp = states->snext;
|
|
zfree(states, sizeof(*states));
|
|
states = sp;
|
|
} else
|
|
ret = 1;
|
|
}
|
|
first = 0;
|
|
}
|
|
ca_xor = cax;
|
|
ca_parsed = 1;
|
|
ca_laststate.snext = states;
|
|
|
|
return ret;
|
|
}
|
|
return 1;
|
|
|
|
case 'D':
|
|
/* This returns the descriptions, actions and sub-contexts for the
|
|
* things _arguments has to execute at this place on the line (the
|
|
* sub-contexts are used as tags).
|
|
* The return value is particularly important here, it says if
|
|
* there are arguments to completely at all. */
|
|
{
|
|
LinkList descr, act, subc;
|
|
Caarg arg;
|
|
int ign = 0, ret = 1;
|
|
|
|
descr = newlinklist();
|
|
act = newlinklist();
|
|
subc = newlinklist();
|
|
|
|
while (lstate) {
|
|
arg = lstate->def;
|
|
|
|
if (arg) {
|
|
ret = 0;
|
|
if (!ign && lstate->doff > 0) {
|
|
ign = 1;
|
|
ignore_prefix(lstate->doff);
|
|
}
|
|
ca_set_data(descr, act, subc, arg->opt, arg,
|
|
lstate->curopt, (lstate->doff > 0));
|
|
}
|
|
lstate = lstate->snext;
|
|
}
|
|
if (!ret) {
|
|
set_list_array(args[1], descr);
|
|
set_list_array(args[2], act);
|
|
set_list_array(args[3], subc);
|
|
}
|
|
return ret;
|
|
}
|
|
case 'O':
|
|
/* This returns the descriptions for the options in the arrays whose
|
|
* names are given as arguments. The descriptions are strings in a
|
|
* form usable by _describe. The return value says if there are any
|
|
* options to be completed. */
|
|
{
|
|
LinkList next = newlinklist();
|
|
LinkList direct = newlinklist();
|
|
LinkList odirect = newlinklist();
|
|
LinkList equal = newlinklist(), l;
|
|
LinkNode node;
|
|
Caopt p;
|
|
char *str;
|
|
int ret = 1;
|
|
|
|
for (; lstate; lstate = lstate->snext) {
|
|
if (lstate->actopts &&
|
|
(lstate->opt || (lstate->doff && lstate->def) ||
|
|
(lstate->def && lstate->def->opt &&
|
|
(lstate->def->type == CAA_OPT ||
|
|
(lstate->def->type >= CAA_RARGS &&
|
|
lstate->def->num < 0)))) &&
|
|
(!lstate->def || lstate->def->type < CAA_RARGS ||
|
|
(lstate->def->type == CAA_RARGS ?
|
|
(lstate->curpos == lstate->argbeg + 1) :
|
|
(compcurrent == 1)))) {
|
|
ret = 0;
|
|
for (p = lstate->d->opts; p; p = p->next) {
|
|
if (p->active && !p->not) {
|
|
switch (p->type) {
|
|
case CAO_NEXT: l = next; break;
|
|
case CAO_DIRECT: l = direct; break;
|
|
case CAO_ODIRECT: l = odirect; break;
|
|
default: l = equal; break;
|
|
}
|
|
if (p->descr) {
|
|
char *n = bslashcolon(p->name);
|
|
int len = strlen(n) + strlen(p->descr) + 2;
|
|
|
|
str = (char *) zhalloc(len);
|
|
strcpy(str, n);
|
|
strcat(str, ":");
|
|
strcat(str, p->descr);
|
|
} else
|
|
str = bslashcolon(p->name);
|
|
|
|
for (node = firstnode(l); node; incnode(node))
|
|
if (!strcmp(str, (char *) getdata(node)))
|
|
break;
|
|
|
|
if (!node)
|
|
addlinknode(l, str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!ret) {
|
|
set_list_array(args[1], next);
|
|
set_list_array(args[2], direct);
|
|
set_list_array(args[3], odirect);
|
|
set_list_array(args[4], equal);
|
|
|
|
return 0;
|
|
}
|
|
return (ca_laststate.singles ? 2 : 1);
|
|
}
|
|
case 'L':
|
|
/* This tests if the beginning of the current word matches an option.
|
|
* It is for cases like `./configure --pre=/<TAB>' which should
|
|
* complete to `--prefix=/...'. The options name isn't fully typed
|
|
* and _arguments finds out that there is no option `--pre' and that
|
|
* it should complete some argument to an option. It then uses -L
|
|
* to find the option the argument is for. */
|
|
{
|
|
LinkList descr, act, subc;
|
|
Caopt opt;
|
|
int ret = 1;
|
|
|
|
descr = newlinklist();
|
|
act = newlinklist();
|
|
subc = newlinklist();
|
|
|
|
while (lstate) {
|
|
opt = ca_get_opt(lstate->d, args[1], 1, NULL);
|
|
|
|
if (opt && opt->args) {
|
|
ret = 0;
|
|
ca_set_data(descr, act, subc, opt->name, opt->args, opt, 1);
|
|
}
|
|
lstate = lstate->snext;
|
|
}
|
|
if (!ret) {
|
|
set_list_array(args[2], descr);
|
|
set_list_array(args[3], act);
|
|
set_list_array(args[4], subc);
|
|
}
|
|
return ret;
|
|
}
|
|
case 's':
|
|
/* This returns zero if we are completing single letter options.
|
|
* It also uses its argument as the name of a parameter and sets
|
|
* that to a string describing the argument behaviour of the last
|
|
* option in the current word so that we can get the auto-suffix
|
|
* right. */
|
|
for (; lstate; lstate = lstate->snext)
|
|
if (lstate->d->single && lstate->singles &&
|
|
lstate->actopts
|
|
#if 0
|
|
/* let's try without, for the -W option of _arguments */
|
|
&& lstate->opt
|
|
#endif
|
|
) {
|
|
setsparam(args[1],
|
|
ztrdup((lstate->ddef && lstate->dopt) ?
|
|
(lstate->dopt->type == CAO_DIRECT ?
|
|
"direct" :
|
|
((lstate->dopt->type == CAO_OEQUAL ||
|
|
lstate->dopt->type == CAO_EQUAL) ?
|
|
"equal" : "next")) : ""));
|
|
return 0;
|
|
}
|
|
return 1;
|
|
case 'M':
|
|
/* This returns the match specs defined for the set of specs we are
|
|
* using. Returned, as usual in a parameter whose name is given as
|
|
* the argument. */
|
|
setsparam(args[1], ztrdup(ca_laststate.d->match));
|
|
return 0;
|
|
case 'a':
|
|
/* This just sets the return value. To zero if there would be or
|
|
* were any normal arguments to be completed. Used to decide if
|
|
* _arguments should say `no arguments' or `no more arguments'. */
|
|
for (; lstate; lstate = lstate->snext)
|
|
if (lstate->d->args || lstate->d->rest)
|
|
return 0;
|
|
return 1;
|
|
case 'W':
|
|
/* This gets two parameter names as arguments. The first is set to
|
|
* the current word sans any option prefixes handled by comparguments.
|
|
* The second parameter is set to an array containing the options on
|
|
* the line and their arguments. I.e. the stuff _arguments returns
|
|
* to its caller in the `line' and `opt_args' parameters. */
|
|
{
|
|
Castate s;
|
|
char **ret, **p;
|
|
LinkNode n;
|
|
LinkList *a;
|
|
Caopt o;
|
|
int num;
|
|
|
|
for (num = 0, s = lstate; s; s = s->snext)
|
|
num += countlinknodes(s->args);
|
|
|
|
ret = p = zalloc((num + 1) * sizeof(char *));
|
|
|
|
for (s = lstate; s; s = s->snext)
|
|
for (n = firstnode(s->args); n; incnode(n))
|
|
*p++ = ztrdup((char *) getdata(n));
|
|
*p = NULL;
|
|
|
|
setaparam(args[1], ret);
|
|
|
|
for (num = 0, s = lstate; s; s = s->snext)
|
|
for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
|
|
if (*a)
|
|
num += 2;
|
|
|
|
ret = p = zalloc((num + 1) * sizeof(char *));
|
|
|
|
for (s = lstate; s; s = s->snext)
|
|
for (o = s->d->opts, a = s->oargs; o; o = o->next, a++)
|
|
if (*a) {
|
|
*p++ = (o->set ? tricat(o->set, o->name, "") :
|
|
ztrdup(o->name));
|
|
*p++ = ca_colonlist(*a);
|
|
}
|
|
*p = NULL;
|
|
|
|
sethparam(args[2], ret);
|
|
}
|
|
return 0;
|
|
case 'n':
|
|
/*
|
|
* This returns the array index of the word where normal
|
|
* arguments began. It uses optbeg rather than nargbeg
|
|
* (the value used when parsing) because nargbeg is assigned
|
|
* to optbeg in the returned value and nargbeg isn't
|
|
* used.
|
|
*
|
|
* -->PLEASE DON'T ASK<--
|
|
*
|
|
* Thank you.
|
|
*/
|
|
setiparam(args[1], (zlong)ca_laststate.optbeg + !isset(KSHARRAYS));
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Help for `_values'. */
|
|
|
|
typedef struct cvdef *Cvdef;
|
|
typedef struct cvval *Cvval;
|
|
|
|
/* Definitions for _values. */
|
|
|
|
struct cvdef {
|
|
char *descr; /* global description */
|
|
int hassep; /* multiple values allowed */
|
|
char sep; /* separator character */
|
|
char argsep; /* argument separator */
|
|
Cvdef next; /* next in cache */
|
|
Cvval vals; /* value definitions */
|
|
char **defs; /* original strings */
|
|
int ndefs; /* number of ... */
|
|
int lastt; /* last time used */
|
|
int words; /* if to look at other words */
|
|
};
|
|
|
|
/* One value definition. */
|
|
|
|
struct cvval {
|
|
Cvval next;
|
|
char *name; /* value name */
|
|
char *descr; /* description */
|
|
char **xor; /* xor-list */
|
|
int type; /* CVV_* below */
|
|
Caarg arg; /* argument definition */
|
|
int active; /* still allowed */
|
|
};
|
|
|
|
#define CVV_NOARG 0
|
|
#define CVV_ARG 1
|
|
#define CVV_OPT 2
|
|
|
|
/* Cache. */
|
|
|
|
#define MAX_CVCACHE 8
|
|
static Cvdef cvdef_cache[MAX_CVCACHE];
|
|
|
|
/* Memory stuff. */
|
|
|
|
static void
|
|
freecvdef(Cvdef d)
|
|
{
|
|
if (d) {
|
|
Cvval p, n;
|
|
|
|
zsfree(d->descr);
|
|
if (d->defs)
|
|
freearray(d->defs);
|
|
|
|
for (p = d->vals; p; p = n) {
|
|
n = p->next;
|
|
zsfree(p->name);
|
|
zsfree(p->descr);
|
|
if (p->xor)
|
|
freearray(p->xor);
|
|
freecaargs(p->arg);
|
|
zfree(p, sizeof(*p));
|
|
}
|
|
zfree(d, sizeof(*d));
|
|
}
|
|
}
|
|
|
|
/* Parse option definitions. */
|
|
|
|
static Cvdef
|
|
parse_cvdef(char *nam, char **args)
|
|
{
|
|
Cvdef ret;
|
|
Cvval val, *valp;
|
|
Caarg arg;
|
|
char **oargs = args, sep = '\0', asep = '=', *name, *descr, *p, *q, **xor, c;
|
|
int xnum, multi, vtype, hassep = 0, words = 0;
|
|
|
|
while (args && args[0] && args[1] &&
|
|
args[0][0] == '-' &&
|
|
(args[0][1] == 's' || args[0][1] == 'S' || args[0][1] == 'w') &&
|
|
!args[0][2]) {
|
|
|
|
if (args[0][1] == 's') {
|
|
hassep = 1;
|
|
sep = args[1][0];
|
|
args += 2;
|
|
} else if (args[0][1] == 'S') {
|
|
asep = args[1][0];
|
|
args += 2;
|
|
} else {
|
|
words = 1;
|
|
args++;
|
|
}
|
|
}
|
|
if (!args[0] || !args[1]) {
|
|
zwarnnam(nam, "not enough arguments");
|
|
return NULL;
|
|
}
|
|
descr = *args++;
|
|
|
|
ret = (Cvdef) zalloc(sizeof(*ret));
|
|
ret->descr = ztrdup(descr);
|
|
ret->hassep = hassep;
|
|
ret->sep = sep;
|
|
ret->argsep = asep;
|
|
ret->next = NULL;
|
|
ret->vals = NULL;
|
|
ret->defs = zarrdup(oargs);
|
|
ret->ndefs = arrlen(oargs);
|
|
ret->lastt = time(0);
|
|
ret->words = words;
|
|
|
|
for (valp = &(ret->vals); *args; args++) {
|
|
int bs = 0;
|
|
p = dupstring(*args);
|
|
xnum = 0;
|
|
|
|
/* xor list? */
|
|
if (*p == '(') {
|
|
LinkList list = newlinklist();
|
|
LinkNode node;
|
|
char **xp, sav;
|
|
|
|
while (*p && *p != ')') {
|
|
for (p++; inblank(*p); p++);
|
|
|
|
if (*p == ')')
|
|
break;
|
|
for (q = p++; *p && *p != ')' && !inblank(*p); p++);
|
|
|
|
if (!*p)
|
|
break;
|
|
|
|
sav = *p;
|
|
*p = '\0';
|
|
addlinknode(list, dupstring(q));
|
|
xnum++;
|
|
*p = sav;
|
|
}
|
|
if (*p != ')') {
|
|
freecvdef(ret);
|
|
zwarnnam(nam, "invalid argument: %s", *args);
|
|
return NULL;
|
|
}
|
|
xor = (char **) zalloc((xnum + 2) * sizeof(char *));
|
|
for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
|
|
*xp = ztrdup((char *) getdata(node));
|
|
xp[0] = xp[1] = NULL;
|
|
|
|
p++;
|
|
} else
|
|
xor = NULL;
|
|
|
|
/* More than once allowed? */
|
|
if ((multi = (*p == '*')))
|
|
p++;
|
|
|
|
/* Skip option name. */
|
|
|
|
for (name = p; *p && *p != ':' && *p != '['; p++)
|
|
if (*p == '\\' && p[1])
|
|
p++, bs = 1;
|
|
|
|
if (hassep && !sep && name + bs + 1 < p) {
|
|
freecvdef(ret);
|
|
zwarnnam(nam, "no multi-letter values with empty separator allowed");
|
|
return NULL;
|
|
}
|
|
/* Optional description? */
|
|
|
|
if ((c = *p) == '[') {
|
|
*p = '\0';
|
|
for (descr = ++p; *p && *p != ']'; p++)
|
|
if (*p == '\\' && p[1])
|
|
p++;
|
|
|
|
if (!*p) {
|
|
freecvdef(ret);
|
|
zwarnnam(nam, "invalid value definition: %s", *args);
|
|
return NULL;
|
|
}
|
|
*p++ = '\0';
|
|
c = *p;
|
|
} else {
|
|
*p = '\0';
|
|
descr = NULL;
|
|
}
|
|
if (c && c != ':') {
|
|
freecvdef(ret);
|
|
zwarnnam(nam, "invalid value definition: %s", *args);
|
|
return NULL;
|
|
}
|
|
/* Get argument? */
|
|
|
|
if (c == ':') {
|
|
if (hassep && !sep) {
|
|
freecvdef(ret);
|
|
zwarnnam(nam, "no value with argument with empty separator allowed");
|
|
return NULL;
|
|
}
|
|
if (*++p == ':') {
|
|
p++;
|
|
vtype = CVV_OPT;
|
|
} else
|
|
vtype = CVV_ARG;
|
|
arg = parse_caarg(0, 0, 0, 0, name, &p, NULL);
|
|
} else {
|
|
vtype = CVV_NOARG;
|
|
arg = NULL;
|
|
}
|
|
if (!multi) {
|
|
if (!xor) {
|
|
xor = (char **) zalloc(2 * sizeof(char *));
|
|
xor[1] = NULL;
|
|
}
|
|
xor[xnum] = ztrdup(name);
|
|
}
|
|
*valp = val = (Cvval) zalloc(sizeof(*val));
|
|
valp = &((*valp)->next);
|
|
|
|
val->next = NULL;
|
|
val->name = ztrdup(name);
|
|
val->descr = ztrdup(descr);
|
|
val->xor = xor;
|
|
val->type = vtype;
|
|
val->arg = arg;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Get the definition from the cache or newly built. */
|
|
|
|
static Cvdef
|
|
get_cvdef(char *nam, char **args)
|
|
{
|
|
Cvdef *p, *min, new;
|
|
int i, na = arrlen(args);
|
|
|
|
for (i = MAX_CVCACHE, p = cvdef_cache, min = NULL; *p && i--; p++)
|
|
if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
|
|
(*p)->lastt = time(0);
|
|
|
|
return *p;
|
|
} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
|
|
min = p;
|
|
if (i > 0)
|
|
min = p;
|
|
if ((new = parse_cvdef(nam, args))) {
|
|
freecvdef(*min);
|
|
*min = new;
|
|
}
|
|
return new;
|
|
}
|
|
|
|
/* Get the definition for a value. */
|
|
|
|
static Cvval
|
|
cv_get_val(Cvdef d, char *name)
|
|
{
|
|
Cvval p;
|
|
|
|
for (p = d->vals; p; p = p->next)
|
|
if (!strcmp(name, p->name))
|
|
return p;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Cvval
|
|
cv_quote_get_val(Cvdef d, char *name)
|
|
{
|
|
int ne;
|
|
|
|
/* remove quotes */
|
|
name = dupstring(name);
|
|
ne = noerrs;
|
|
noerrs = 2;
|
|
parse_subst_string(name);
|
|
noerrs = ne;
|
|
remnulargs(name);
|
|
untokenize(name);
|
|
|
|
return cv_get_val(d, name);
|
|
}
|
|
|
|
/* Handle a xor list. */
|
|
|
|
static void
|
|
cv_inactive(Cvdef d, char **xor)
|
|
{
|
|
if (xor) {
|
|
Cvval val;
|
|
|
|
for (; *xor; xor++)
|
|
if ((val = cv_get_val(d, *xor)))
|
|
val->active = 0;
|
|
}
|
|
}
|
|
|
|
/* Parse state. */
|
|
|
|
struct cvstate {
|
|
Cvdef d;
|
|
Caarg def;
|
|
Cvval val;
|
|
LinkList vals;
|
|
};
|
|
|
|
static struct cvstate cv_laststate;
|
|
static int cv_parsed = 0, cv_alloced = 0;
|
|
|
|
/* Get the next value in the string. Return it's definition and update the
|
|
* sp pointer to point to the end of the value (plus argument, if any).
|
|
* If there is no next value, the string pointer is set to null. In any
|
|
* case ap will point to the beginning of the argument or will be a null
|
|
* pointer if there is no argument.
|
|
*/
|
|
|
|
static Cvval
|
|
cv_next(Cvdef d, char **sp, char **ap)
|
|
{
|
|
Cvval r = NULL;
|
|
char *s = *sp;
|
|
|
|
if (!*s) {
|
|
*sp = *ap = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
if ((d->hassep && !d->sep) || !d->argsep) {
|
|
char sav, ec, *v = s, *os;
|
|
|
|
ec = ((d->hassep && d->sep) ? d->sep : d->argsep);
|
|
|
|
do {
|
|
sav = *++s;
|
|
*s = '\0';
|
|
if ((r = cv_quote_get_val(d, v))) {
|
|
*s = sav;
|
|
|
|
break;
|
|
}
|
|
*s = sav;
|
|
} while (*s && *s != ec);
|
|
|
|
os = s;
|
|
|
|
if (d->hassep && d->sep) {
|
|
if ((s = strchr(s, d->sep)))
|
|
*sp = s + 1;
|
|
else
|
|
*sp = NULL;
|
|
} else
|
|
*sp = s;
|
|
if (d->argsep && *os == d->argsep) {
|
|
*ap = os + 1;
|
|
*sp = NULL;
|
|
} else if (r && r->type != CVV_NOARG)
|
|
*ap = os;
|
|
else
|
|
*ap = NULL;
|
|
|
|
return r;
|
|
|
|
} else if (d->hassep) {
|
|
char *ns = strchr(s, d->sep), *as = 0, *sap, sav = 0;
|
|
int skip = 0;
|
|
|
|
if (d->argsep && (as = strchr(s, d->argsep)) && (!ns || as <= ns)) {
|
|
*ap = as + 1;
|
|
ns = strchr(as + 1, d->sep);
|
|
skip = 1;
|
|
sap = as;
|
|
} else {
|
|
*ap = NULL;
|
|
sap = ns;
|
|
}
|
|
if (sap) {
|
|
sav = *sap;
|
|
*sap = '\0';
|
|
}
|
|
if ((!(r = cv_quote_get_val(d, s)) || r->type == CVV_NOARG) && skip)
|
|
ns = as;
|
|
|
|
if (sap)
|
|
*sap = sav;
|
|
|
|
*sp = ((!ns || (ns == as && r && r->type != CVV_NOARG)) ? NULL : ns + 1);
|
|
|
|
return r;
|
|
} else {
|
|
char *as = strchr(s, d->argsep), *sap, sav = 0;
|
|
|
|
*sp = NULL;
|
|
|
|
if (as) {
|
|
*ap = as + 1;
|
|
sap = as;
|
|
sav = *as;
|
|
*sap = '\0';
|
|
} else
|
|
*ap = sap = NULL;
|
|
|
|
r = cv_quote_get_val(d, s);
|
|
|
|
if (sap)
|
|
*sap = sav;
|
|
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/* Parse the current word. */
|
|
|
|
static void
|
|
cv_parse_word(Cvdef d)
|
|
{
|
|
Cvval val;
|
|
struct cvstate state;
|
|
char *str, *arg = NULL, *pign = compprefix;
|
|
int nosfx = 0;
|
|
|
|
if (cv_alloced)
|
|
freelinklist(cv_laststate.vals, freestr);
|
|
|
|
for (val = d->vals; val; val = val->next)
|
|
val->active = 1;
|
|
|
|
state.d = d;
|
|
state.def = NULL;
|
|
state.val = NULL;
|
|
state.vals = (LinkList) znewlinklist();
|
|
|
|
cv_alloced = 1;
|
|
|
|
if (d->words && compwords[0]) {
|
|
int i;
|
|
|
|
for (i = 1; compwords[i]; i++)
|
|
if (i != compcurrent - 1)
|
|
for (str = compwords[i]; str && *str; ) {
|
|
if ((val = cv_next(d, &str, &arg))) {
|
|
zaddlinknode(state.vals, ztrdup(val->name));
|
|
if (arg) {
|
|
char sav = '\0';
|
|
|
|
if (str) {
|
|
sav = str[-1];
|
|
str[-1] = '\0';
|
|
}
|
|
zaddlinknode(state.vals, ztrdup(arg));
|
|
if (str)
|
|
str[-1] = sav;
|
|
} else
|
|
zaddlinknode(state.vals, ztrdup(""));
|
|
|
|
if (i + 1 < compcurrent)
|
|
cv_inactive(d, val->xor);
|
|
}
|
|
}
|
|
|
|
val = NULL;
|
|
arg = NULL;
|
|
}
|
|
for (str = compprefix; str && *str; ) {
|
|
if ((val = cv_next(d, &str, &arg))) {
|
|
zaddlinknode(state.vals, ztrdup(val->name));
|
|
if (arg) {
|
|
if (str) {
|
|
char sav = str[-1];
|
|
|
|
str[-1] = '\0';
|
|
zaddlinknode(state.vals, ztrdup(arg));
|
|
str[-1] = sav;
|
|
} else {
|
|
zaddlinknode(state.vals, tricat(arg, compsuffix, ""));
|
|
nosfx = 1;
|
|
}
|
|
} else
|
|
zaddlinknode(state.vals, ztrdup(""));
|
|
|
|
cv_inactive(d, val->xor);
|
|
|
|
if (str)
|
|
pign = str;
|
|
else
|
|
val->active = 1;
|
|
}
|
|
}
|
|
state.val = val;
|
|
if (val && arg && !str)
|
|
state.def = val->arg;
|
|
|
|
if (!nosfx && d->hassep) {
|
|
int ign = 0;
|
|
char *more = NULL;
|
|
|
|
ignore_prefix(pign - compprefix);
|
|
|
|
if (!d->sep && (!val || val->type == CVV_NOARG)) {
|
|
ign = strlen(compsuffix);
|
|
more = compsuffix;
|
|
} else {
|
|
if (d->sep) {
|
|
char *ns = strchr(compsuffix, d->sep), *as;
|
|
|
|
if (d->argsep && (as = strchr(compsuffix, d->argsep)) &&
|
|
(!ns || as <= ns)) {
|
|
ign = strlen(as);
|
|
} else
|
|
ign = (ns ? strlen(ns) : 0);
|
|
|
|
more = (ns ? ns + 1 : NULL);
|
|
} else if (d->argsep) {
|
|
char *as;
|
|
|
|
if ((as = strchr(compsuffix, d->argsep)))
|
|
ign = strlen(as);
|
|
}
|
|
}
|
|
more = dupstring(more);
|
|
|
|
if (ign)
|
|
ignore_suffix(ign);
|
|
|
|
while (more && *more) {
|
|
if ((val = cv_next(d, &more, &arg))) {
|
|
zaddlinknode(state.vals, ztrdup(val->name));
|
|
if (arg) {
|
|
if (more) {
|
|
char sav = more[-1];
|
|
|
|
more[-1] = '\0';
|
|
zaddlinknode(state.vals, ztrdup(arg));
|
|
more[-1] = sav;
|
|
} else {
|
|
zaddlinknode(state.vals, tricat(arg, compsuffix, ""));
|
|
nosfx = 1;
|
|
}
|
|
} else
|
|
zaddlinknode(state.vals, ztrdup(""));
|
|
|
|
cv_inactive(d, val->xor);
|
|
}
|
|
}
|
|
} else if (arg)
|
|
ignore_prefix(arg - compprefix);
|
|
else
|
|
ignore_prefix(pign - compprefix);
|
|
|
|
memcpy(&cv_laststate, &state, sizeof(state));
|
|
}
|
|
|
|
static int
|
|
bin_compvalues(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
int min, max, n;
|
|
|
|
if (incompfunc != 1) {
|
|
zwarnnam(nam, "can only be called from completion function");
|
|
return 1;
|
|
}
|
|
if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
|
|
zwarnnam(nam, "invalid argument: %s", args[0]);
|
|
return 1;
|
|
}
|
|
if (args[0][1] != 'i' && !cv_parsed) {
|
|
zwarnnam(nam, "no parsed state");
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'i': min = 2; max = -1; break;
|
|
case 'D': min = 2; max = 2; break;
|
|
case 'C': min = 1; max = 1; break;
|
|
case 'V': min = 3; max = 3; break;
|
|
case 's': min = 1; max = 1; break;
|
|
case 'S': min = 1; max = 1; break;
|
|
case 'd': min = 1; max = 1; break;
|
|
case 'L': min = 3; max = 4; break;
|
|
case 'v': min = 1; max = 1; break;
|
|
default:
|
|
zwarnnam(nam, "invalid option: %s", args[0]);
|
|
return 1;
|
|
}
|
|
n = arrlen(args) - 1;
|
|
if (n < min) {
|
|
zwarnnam(nam, "not enough arguments");
|
|
return 1;
|
|
} else if (max >= 0 && n > max) {
|
|
zwarnnam(nam, "too many arguments");
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'i':
|
|
/* This initialises the internal data structures. The arguments are
|
|
* just the arguments that were given to _values itself. */
|
|
{
|
|
Cvdef def = get_cvdef(nam, args + 1);
|
|
int cvp = cv_parsed;
|
|
|
|
cv_parsed = 0;
|
|
|
|
if (!def)
|
|
return 1;
|
|
|
|
cv_parsed = cvp;
|
|
cv_parse_word(def);
|
|
cv_parsed = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
case 'D':
|
|
/* This returns the description and action to use if we are at
|
|
* a place where some action has to be used at all. In that case
|
|
* zero is returned and non-zero otherwise. */
|
|
{
|
|
Caarg arg = cv_laststate.def;
|
|
|
|
if (arg) {
|
|
setsparam(args[1], ztrdup(arg->descr));
|
|
setsparam(args[2], ztrdup(arg->action));
|
|
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
case 'C':
|
|
/* This returns the sub-context (i.e.: the tag) to use when executing
|
|
* an action. */
|
|
{
|
|
Caarg arg = cv_laststate.def;
|
|
|
|
if (arg) {
|
|
setsparam(args[1], ztrdup(arg->opt));
|
|
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
case 'V':
|
|
/* This is what -O is for comparguments: it returns (in three arrays)
|
|
* the values for values without arguments, with arguments and with
|
|
* optional arguments (so that we can get the auto-suffixes right).
|
|
* As for comparguments, the strings returned are usable for _describe. */
|
|
{
|
|
LinkList noarg = newlinklist();
|
|
LinkList arg = newlinklist();
|
|
LinkList opt = newlinklist(), l;
|
|
Cvval p;
|
|
char *str;
|
|
|
|
for (p = cv_laststate.d->vals; p; p = p->next) {
|
|
if (p->active) {
|
|
switch (p->type) {
|
|
case CVV_NOARG: l = noarg; break;
|
|
case CVV_ARG: l = arg; break;
|
|
default: l = opt; break;
|
|
}
|
|
if (p->descr) {
|
|
int len = strlen(p->name) + strlen(p->descr) + 2;
|
|
|
|
str = (char *) zhalloc(len);
|
|
strcpy(str, p->name);
|
|
strcat(str, ":");
|
|
strcat(str, p->descr);
|
|
} else
|
|
str = p->name;
|
|
addlinknode(l, str);
|
|
}
|
|
}
|
|
set_list_array(args[1], noarg);
|
|
set_list_array(args[2], arg);
|
|
set_list_array(args[3], opt);
|
|
|
|
return 0;
|
|
}
|
|
case 's':
|
|
/* This returns the value separator, if any, and sets the return
|
|
* value to say if there is such a separator. */
|
|
if (cv_laststate.d->hassep) {
|
|
char tmp[2];
|
|
|
|
tmp[0] = cv_laststate.d->sep;
|
|
tmp[1] = '\0';
|
|
setsparam(args[1], ztrdup(tmp));
|
|
|
|
return 0;
|
|
}
|
|
return 1;
|
|
case 'S':
|
|
/* Like -s, but for the separator between values and their arguments. */
|
|
{
|
|
char tmp[2];
|
|
|
|
tmp[0] = cv_laststate.d->argsep;
|
|
tmp[1] = '\0';
|
|
setsparam(args[1], ztrdup(tmp));
|
|
}
|
|
return 0;
|
|
case 'd':
|
|
/* This returns the description string (first argument to _values)
|
|
* which is passed down to _describe. */
|
|
setsparam(args[1], ztrdup(cv_laststate.d->descr));
|
|
return 0;
|
|
case 'L':
|
|
/* Almost the same as for comparguments. This gets a value name
|
|
* and returns the description and action of its first argument, if
|
|
* any. The rest (prefix matching) is in _values. Return non-zero
|
|
* if there is no such option. */
|
|
{
|
|
Cvval val = cv_get_val(cv_laststate.d, args[1]);
|
|
|
|
if (val && val->arg) {
|
|
setsparam(args[2], val->arg->descr);
|
|
setsparam(args[3], val->arg->action);
|
|
|
|
if (args[4])
|
|
setsparam(args[4], ztrdup(val->name));
|
|
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
case 'v':
|
|
/* Again, as for comparguments. This returns the values and their
|
|
* arguments as an array which will be stored in val_args in _values. */
|
|
if (cv_laststate.vals) {
|
|
char **ret;
|
|
|
|
ret = zlinklist2array(cv_laststate.vals);
|
|
sethparam(args[1], ret);
|
|
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static char *
|
|
comp_quote(char *str, int prefix)
|
|
{
|
|
int x;
|
|
char *ret;
|
|
|
|
if ((x = (prefix && *str == '=')))
|
|
*str = 'x';
|
|
|
|
ret = quotestring(str, NULL, *compqstack);
|
|
|
|
if (x)
|
|
*str = *ret = '=';
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
bin_compquote(char *nam, char **args, Options ops, UNUSED(int func))
|
|
{
|
|
char *name;
|
|
struct value vbuf;
|
|
Value v;
|
|
|
|
if (incompfunc != 1) {
|
|
zwarnnam(nam, "can only be called from completion function");
|
|
return 1;
|
|
}
|
|
/* Anything to do? */
|
|
|
|
if (!compqstack || !*compqstack)
|
|
return 0;
|
|
|
|
/* For all parameters given... */
|
|
|
|
while ((name = *args++)) {
|
|
name = dupstring(name);
|
|
queue_signals();
|
|
if ((v = getvalue(&vbuf, &name, 0))) {
|
|
switch (PM_TYPE(v->pm->node.flags)) {
|
|
case PM_SCALAR:
|
|
setstrvalue(v, ztrdup(comp_quote(getstrvalue(v),
|
|
OPT_ISSET(ops,'p'))));
|
|
break;
|
|
case PM_ARRAY:
|
|
{
|
|
char **val = v->pm->gsu.a->getfn(v->pm);
|
|
char **new = (char **) zalloc((arrlen(val) + 1) *
|
|
sizeof(char *));
|
|
char **p = new;
|
|
|
|
for (; *val; val++, p++)
|
|
*p = ztrdup(comp_quote(*val, OPT_ISSET(ops,'p')));
|
|
*p = NULL;
|
|
|
|
setarrvalue(v, new);
|
|
}
|
|
break;
|
|
default:
|
|
zwarnnam(nam, "invalid parameter type: %s", args[-1]);
|
|
}
|
|
} else
|
|
zwarnnam(nam, "unknown parameter: %s", args[-1]);
|
|
unqueue_signals();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Tags stuff. */
|
|
|
|
typedef struct ctags *Ctags;
|
|
typedef struct ctset *Ctset;
|
|
|
|
/* A bunch of tag sets. */
|
|
|
|
struct ctags {
|
|
char **all; /* all tags offered */
|
|
char *context; /* the current context */
|
|
int init; /* not yet used */
|
|
Ctset sets; /* the tag sets */
|
|
};
|
|
|
|
/* A tag set. */
|
|
|
|
struct ctset {
|
|
Ctset next;
|
|
char **tags; /* the tags */
|
|
char *tag; /* last tag checked for -A */
|
|
char **ptr; /* ptr into tags for -A */
|
|
};
|
|
|
|
/* Array of tag-set infos. Index is the locallevel. */
|
|
|
|
#define MAX_TAGS 256
|
|
static Ctags comptags[MAX_TAGS];
|
|
|
|
/* locallevel at last comptags -i */
|
|
|
|
static int lasttaglevel;
|
|
|
|
static void
|
|
freectset(Ctset s)
|
|
{
|
|
Ctset n;
|
|
|
|
while (s) {
|
|
n = s->next;
|
|
|
|
if (s->tags)
|
|
freearray(s->tags);
|
|
zsfree(s->tag);
|
|
zfree(s, sizeof(*s));
|
|
|
|
s = n;
|
|
}
|
|
}
|
|
|
|
static void
|
|
freectags(Ctags t)
|
|
{
|
|
if (t) {
|
|
if (t->all)
|
|
freearray(t->all);
|
|
zsfree(t->context);
|
|
freectset(t->sets);
|
|
zfree(t, sizeof(*t));
|
|
}
|
|
}
|
|
|
|
/* Set the tags for the current local level. */
|
|
|
|
static void
|
|
settags(int level, char **tags)
|
|
{
|
|
Ctags t;
|
|
|
|
if (comptags[level])
|
|
freectags(comptags[level]);
|
|
|
|
comptags[level] = t = (Ctags) zalloc(sizeof(*t));
|
|
|
|
t->all = zarrdup(tags + 1);
|
|
t->context = ztrdup(*tags);
|
|
t->sets = NULL;
|
|
t->init = 1;
|
|
}
|
|
|
|
/* Check if an array contains a string. */
|
|
|
|
/**/
|
|
static int
|
|
arrcontains(char **a, char *s, int colon)
|
|
{
|
|
char *p, *q;
|
|
|
|
while (*a) {
|
|
if (colon) {
|
|
for (p = s, q = *a++; *p && *q && *p != ':' && *q != ':'; p++, q++)
|
|
if (*p != *q)
|
|
break;
|
|
if ((!*p || *p == ':') && (!*q || *q == ':'))
|
|
return 1;
|
|
} else if (!strcmp(*a++, s))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bin_comptags(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
int min, max, n, level;
|
|
|
|
if (incompfunc != 1) {
|
|
zwarnnam(nam, "can only be called from completion function");
|
|
return 1;
|
|
}
|
|
if (args[0][0] != '-' || !args[0][1] ||
|
|
(args[0][2] && (args[0][2] != '-' || args[0][3]))) {
|
|
zwarnnam(nam, "invalid argument: %s", args[0]);
|
|
return 1;
|
|
}
|
|
level = locallevel - (args[0][2] ? 1 : 0);
|
|
if (level >= MAX_TAGS) {
|
|
zwarnnam(nam, "nesting level too deep");
|
|
return 1;
|
|
}
|
|
if (args[0][1] != 'i' && args[0][1] != 'I' && !comptags[level]) {
|
|
zwarnnam(nam, "no tags registered");
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'i': min = 2; max = -1; break;
|
|
case 'C': min = 1; max = 1; break;
|
|
case 'T': min = 0; max = 0; break;
|
|
case 'N': min = 0; max = 0; break;
|
|
case 'R': min = 1; max = 1; break;
|
|
case 'S': min = 1; max = 1; break;
|
|
case 'A': min = 2; max = 3; break;
|
|
default:
|
|
zwarnnam(nam, "invalid option: %s", args[0]);
|
|
return 1;
|
|
}
|
|
n = arrlen(args) - 1;
|
|
if (n < min) {
|
|
zwarnnam(nam, "not enough arguments");
|
|
return 1;
|
|
} else if (max >= 0 && n > max) {
|
|
zwarnnam(nam, "too many arguments");
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'i':
|
|
settags(level, args + 1);
|
|
lasttaglevel = level;
|
|
break;
|
|
case 'C':
|
|
setsparam(args[1], ztrdup(comptags[level]->context));
|
|
break;
|
|
case 'T':
|
|
return !comptags[level]->sets;
|
|
case 'N':
|
|
{
|
|
Ctset s;
|
|
|
|
if (comptags[level]->init)
|
|
comptags[level]->init = 0;
|
|
else if ((s = comptags[level]->sets)) {
|
|
comptags[level]->sets = s->next;
|
|
s->next = NULL;
|
|
freectset(s);
|
|
}
|
|
return !comptags[level]->sets;
|
|
}
|
|
case 'R':
|
|
{
|
|
Ctset s;
|
|
|
|
return !((s = comptags[level]->sets) &&
|
|
arrcontains(s->tags, args[1], 1));
|
|
}
|
|
case 'A':
|
|
{
|
|
Ctset s;
|
|
|
|
if (comptags[level] && (s = comptags[level]->sets)) {
|
|
char **q, *v = NULL;
|
|
int l = strlen(args[1]);
|
|
|
|
if (!s->tag || strcmp(s->tag, args[1])) {
|
|
zsfree(s->tag);
|
|
s->tag = ztrdup(args[1]);
|
|
s->ptr = s->tags;
|
|
}
|
|
for (q = s->ptr; *q; q++) {
|
|
if (strpfx(args[1], *q)) {
|
|
if (!(*q)[l]) {
|
|
v = *q;
|
|
break;
|
|
} else if ((*q)[l] == ':') {
|
|
v = (*q) + l + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!v) {
|
|
zsfree(s->tag);
|
|
s->tag = NULL;
|
|
return 1;
|
|
}
|
|
s->ptr = q + 1;
|
|
setsparam(args[2], ztrdup(*v == '-' ? dyncat(args[1], v) : v));
|
|
if (args[3]) {
|
|
char *r = dupstring(*q), *p;
|
|
|
|
for (p = r + (v - *q); *p && *p != ':'; p++);
|
|
*p = '\0';
|
|
|
|
setsparam(args[3], ztrdup(r));
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
case 'S':
|
|
if (comptags[level]->sets) {
|
|
char **ret;
|
|
|
|
ret = zarrdup(comptags[level]->sets->tags);
|
|
setaparam(args[1], ret);
|
|
} else
|
|
return 1;
|
|
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bin_comptry(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
if (incompfunc != 1) {
|
|
zwarnnam(nam, "can only be called from completion function");
|
|
return 1;
|
|
}
|
|
if (!lasttaglevel || !comptags[lasttaglevel]) {
|
|
zwarnnam(nam, "no tags registered");
|
|
return 1;
|
|
}
|
|
if (*args) {
|
|
if (!strcmp(*args, "-m")) {
|
|
char *s, *p, *q, *c, **all = comptags[lasttaglevel]->all;
|
|
LinkList list = newlinklist();
|
|
int num = 0;
|
|
Ctset set;
|
|
|
|
while ((s = *++args)) {
|
|
while (*s) {
|
|
while (*s && iblank(*s))
|
|
s++;
|
|
for (p = q = s, c = NULL; *s && !inblank(*s); s++) {
|
|
if (!c && *s == ':')
|
|
c = p;
|
|
if (*s == '\\' && s[1])
|
|
s++;
|
|
*p++ = *s;
|
|
}
|
|
if (*s)
|
|
s++;
|
|
*p = '\0';
|
|
if (*q) {
|
|
char *qq, *qqq;
|
|
|
|
if (c)
|
|
*c = '\0';
|
|
|
|
qqq = qq = dupstring(q);
|
|
while (*qqq) {
|
|
if (*qqq == '\\' && qqq[1])
|
|
qqq++;
|
|
else if (*qqq == '{')
|
|
*qqq = Inbrace;
|
|
else if (*qqq == '}')
|
|
*qqq = Outbrace;
|
|
else if (*qqq == ',')
|
|
*qqq = Comma;
|
|
qqq++;
|
|
}
|
|
tokenize(qq);
|
|
if (haswilds(qq) || hasbraces(qq)) {
|
|
Patprog prog;
|
|
LinkNode bnode, node;
|
|
LinkList blist = newlinklist();
|
|
|
|
addlinknode(blist, qq);
|
|
for (bnode = firstnode(blist); bnode; incnode(bnode))
|
|
while (hasbraces(getdata(bnode)))
|
|
xpandbraces(blist, &bnode);
|
|
|
|
for (bnode = firstnode(blist); bnode; incnode(bnode)) {
|
|
qq = (char *) getdata(bnode);
|
|
if ((prog = patcompile(qq, PAT_STATIC, NULL))) {
|
|
char **a, *n;
|
|
int l = (c ? strlen(c + 1) + 2 : 1), al;
|
|
|
|
for (a = all; *a; a++) {
|
|
for (node = firstnode(list); node;
|
|
incnode(node)) {
|
|
char *as, *ls;
|
|
|
|
for (as = *a, ls = (char *) getdata(node);
|
|
*as && *ls && *ls != ':'; as++, ls++)
|
|
if (*as != *ls)
|
|
break;
|
|
if (!*as && (!*ls || *ls == ':'))
|
|
break;
|
|
}
|
|
if (node)
|
|
continue;
|
|
if (pattry(prog, *a)) {
|
|
n = (char *) zhalloc((al = strlen(*a)) + l);
|
|
strcpy(n, *a);
|
|
if (c) {
|
|
n[al] = ':';
|
|
strcpy(n + al + 1, c + 1);
|
|
}
|
|
addlinknode(list, n);
|
|
num++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (arrcontains(all, q, 0)) {
|
|
for (set = comptags[lasttaglevel]->sets; set;
|
|
set = set->next)
|
|
if (arrcontains(set->tags, q, 0))
|
|
break;
|
|
if (!set) {
|
|
addlinknode(list, q);
|
|
num++;
|
|
}
|
|
}
|
|
if (c)
|
|
*c = ':';
|
|
}
|
|
}
|
|
if (num) {
|
|
Ctset l;
|
|
|
|
set = (Ctset) zalloc(sizeof(*set));
|
|
|
|
set->tags = zlinklist2array(list);
|
|
set->next = NULL;
|
|
set->ptr = NULL;
|
|
set->tag = NULL;
|
|
|
|
if ((l = comptags[lasttaglevel]->sets)) {
|
|
while (l->next)
|
|
l = l->next;
|
|
|
|
l->next = set;
|
|
} else
|
|
comptags[lasttaglevel]->sets = set;
|
|
}
|
|
}
|
|
} else {
|
|
char **p, **q, **all;
|
|
int sep = 0;
|
|
|
|
if ((sep = !strcmp(*args, "-s")))
|
|
args++;
|
|
|
|
for (p = q = args, all = comptags[lasttaglevel]->all; *p; p++)
|
|
if (arrcontains(all, *p, 1)) {
|
|
Ctset s;
|
|
|
|
for (s = comptags[lasttaglevel]->sets; s; s = s->next)
|
|
if (arrcontains(s->tags, *p, 0))
|
|
break;
|
|
|
|
if (!s)
|
|
*q++ = *p;
|
|
}
|
|
*q = NULL;
|
|
|
|
if (*args) {
|
|
char *dummy[2];
|
|
|
|
do {
|
|
Ctset s = (Ctset) zalloc(sizeof(*s)), l;
|
|
|
|
if (sep) {
|
|
dummy[0] = *args++;
|
|
dummy[1] = NULL;
|
|
s->tags = zarrdup(dummy);
|
|
} else
|
|
s->tags = zarrdup(args);
|
|
s->next = NULL;
|
|
s->ptr = NULL;
|
|
s->tag = NULL;
|
|
|
|
if ((l = comptags[lasttaglevel]->sets)) {
|
|
while (l->next)
|
|
l = l->next;
|
|
|
|
l->next = s;
|
|
} else
|
|
comptags[lasttaglevel]->sets = s;
|
|
} while (sep && *args);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define PATH_MAX2 (PATH_MAX * 2)
|
|
|
|
/*
|
|
* Return a list of files we should accept exactly, without
|
|
* trying pattern matching.
|
|
*
|
|
* This is based on the accept-exact style, which may be
|
|
* an array so is passed in via "accept". The trial files
|
|
* are input in "names". "skipped" is passed down straight
|
|
* from the file completion function: it's got something to
|
|
* do with other components in the path but it's hard to work out
|
|
* quite what.
|
|
*
|
|
* There is one extra trick here for Cygwin. Regardless of the style,
|
|
* if the file ends in a colon it has to be a drive or a special device
|
|
* file and we always accept it exactly because treating it as a pattern
|
|
* won't work.
|
|
*/
|
|
static LinkList
|
|
cfp_test_exact(LinkList names, char **accept, char *skipped)
|
|
{
|
|
char buf[PATH_MAX2 + 1], *suf, *p;
|
|
int l, sl, found = 0;
|
|
struct stat st;
|
|
LinkNode node;
|
|
LinkList ret = newlinklist(), alist = NULL;
|
|
#ifdef __CYGWIN__
|
|
int accept_off = 0;
|
|
#endif
|
|
|
|
/*
|
|
* Don't do this unless completion has provided either a
|
|
* prefix or suffix from the command line.
|
|
*/
|
|
if (!(compprefix && *compprefix) && !(compsuffix && *compsuffix))
|
|
return NULL;
|
|
|
|
/*
|
|
* See if accept-exact is off, implicitly or explicitly.
|
|
*/
|
|
if (!accept || !*accept ||
|
|
((!strcmp(*accept, "false") || !strcmp(*accept, "no") ||
|
|
!strcmp(*accept, "off") || !strcmp(*accept, "0")) && !accept[1])) {
|
|
#ifdef __CYGWIN__
|
|
accept_off = 1;
|
|
#else
|
|
/* If not Cygwin, nothing to do here. */
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* See if the style is something other than just a boolean.
|
|
*/
|
|
if (
|
|
#ifdef __CYGWIN__
|
|
!accept_off &&
|
|
#endif
|
|
(accept[1] ||
|
|
(strcmp(*accept, "true") && strcmp(*accept, "yes") &&
|
|
strcmp(*accept, "on") && strcmp(*accept, "1")))) {
|
|
Patprog prog;
|
|
|
|
alist = newlinklist();
|
|
|
|
for (; (p = *accept); accept++) {
|
|
if (*p == '*' && !p[1]) {
|
|
alist = NULL;
|
|
break;
|
|
}
|
|
tokenize(p = dupstring(p));
|
|
if ((prog = patcompile(p, 0, NULL)))
|
|
addlinknode(alist, prog);
|
|
}
|
|
}
|
|
/*
|
|
* Assemble the bits other than the set of file names:
|
|
* the other components, and the prefix and suffix.
|
|
*/
|
|
sl = strlen(skipped) + (compprefix ? strlen(compprefix) : 0) +
|
|
(compsuffix ? strlen(compsuffix) : 0);
|
|
|
|
if (sl > PATH_MAX2)
|
|
return NULL;
|
|
|
|
suf = dyncat(skipped, rembslash(dyncat(compprefix, compsuffix)));
|
|
|
|
for (node = firstnode(names); node; incnode(node)) {
|
|
l = strlen(p = (char *) getdata(node));
|
|
if (l + sl < PATH_MAX2) {
|
|
#ifdef __CYGWIN__
|
|
char *testbuf;
|
|
#define TESTBUF testbuf
|
|
#else
|
|
#define TESTBUF buf
|
|
#endif
|
|
strcpy(buf, p);
|
|
strcpy(buf + l, suf);
|
|
#ifdef __CYGWIN__
|
|
if (accept_off) {
|
|
int sl = strlen(buf);
|
|
/*
|
|
* If accept-exact is not set, accept this only if
|
|
* it looks like a special file such as a drive.
|
|
* We still test if it exists.
|
|
*/
|
|
if (!sl || strchr(buf, '/') || buf[sl-1] != ':')
|
|
continue;
|
|
if (sl == 2) {
|
|
/*
|
|
* Recent versions of Cygwin only recognise "c:/",
|
|
* but not "c:", as special directories. So
|
|
* we have to append the slash for the purpose of
|
|
* the test.
|
|
*/
|
|
testbuf = zhalloc(sl + 2);
|
|
strcpy(testbuf, buf);
|
|
testbuf[sl] = '/';
|
|
testbuf[sl+1] = '\0';
|
|
} else {
|
|
/* Don't do this with stuff like PRN: */
|
|
testbuf = buf;
|
|
}
|
|
} else {
|
|
testbuf = buf;
|
|
}
|
|
#endif
|
|
if (!ztat(TESTBUF, &st, 0)) {
|
|
/*
|
|
* File exists; if accept-exact contained non-boolean
|
|
* values it must match those, too.
|
|
*/
|
|
if (alist) {
|
|
LinkNode anode;
|
|
|
|
for (anode = firstnode(alist); anode; incnode(anode))
|
|
if (pattry((Patprog) getdata(anode), buf))
|
|
break;
|
|
|
|
if (!anode)
|
|
continue;
|
|
}
|
|
found = 1;
|
|
addlinknode(ret, dupstring(buf));
|
|
}
|
|
}
|
|
}
|
|
return (found ? ret : NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* This code constructs (from heap) and returns a string that
|
|
* corresponds to a series of matches; when compiled as a pattern, at
|
|
* each position it matches either the character from the string "add"
|
|
* or the corresponding single-character match from the set of matchers.
|
|
* To take a simple case, if add is "a" and the single matcher for the
|
|
* character position matches "[0-9]", the pattern returned is "[0-9a]".
|
|
* We take account of equivalences between the word and line, too.
|
|
*
|
|
* As there are virtually no comments in this file, I don't really
|
|
* know why we're doing this, but it's to do with a matcher which
|
|
* is passed as an argument to the utility compfiles -p/-P.
|
|
*/
|
|
static char *
|
|
cfp_matcher_range(Cmatcher *ms, char *add)
|
|
{
|
|
Cmatcher *mp, m;
|
|
int len = 0, mt;
|
|
char *ret = NULL, *p = NULL, *adds = add;
|
|
|
|
/*
|
|
* Do this twice: once to work out the length of the
|
|
* string in len, the second time to build it in ret.
|
|
* This is probably worthwhile because otherwise memory
|
|
* management is difficult.
|
|
*/
|
|
for (;;) {
|
|
MB_METACHARINIT();
|
|
for (mp = ms; *add; ) {
|
|
convchar_t addc;
|
|
int addlen;
|
|
|
|
addlen = MB_METACHARLENCONV(add, &addc);
|
|
#ifdef MULTIBYTE_SUPPORT
|
|
if (addc == WEOF)
|
|
addc = (wchar_t)(*p == Meta ? p[1] ^ 32 : *p);
|
|
#endif
|
|
|
|
if (!(m = *mp)) {
|
|
/*
|
|
* No matcher, so just match the character
|
|
* itself.
|
|
*
|
|
* TODO: surely this needs quoting if it's a
|
|
* metacharacter?
|
|
*/
|
|
if (ret) {
|
|
memcpy(p, add, addlen);
|
|
p += addlen;
|
|
} else
|
|
len += addlen;
|
|
} else if (m->flags & CMF_RIGHT) {
|
|
/*
|
|
* Right-anchored: match anything followed
|
|
* by the character itself.
|
|
*/
|
|
if (ret) {
|
|
*p++ = '*';
|
|
/* TODO: quote again? */
|
|
memcpy(p, add, addlen);
|
|
p += addlen;
|
|
} else
|
|
len += addlen + 1;
|
|
} else {
|
|
/* The usual set of matcher possibilities. */
|
|
convchar_t ind;
|
|
if (m->line->tp == CPAT_EQUIV &&
|
|
m->word->tp == CPAT_EQUIV) {
|
|
/*
|
|
* Genuine equivalence. Add the character to match
|
|
* and the equivalent character from the word
|
|
* pattern.
|
|
*
|
|
* TODO: we could be more careful here with special
|
|
* cases as we are in the basic character class
|
|
* code below.
|
|
*/
|
|
if (ret) {
|
|
*p++ = '[';
|
|
memcpy(p, add, addlen);
|
|
p += addlen;
|
|
} else
|
|
len += addlen + 1;
|
|
if (PATMATCHRANGE(m->line->u.str, addc, &ind, &mt)) {
|
|
/*
|
|
* Find the equivalent match for ind in the
|
|
* word pattern.
|
|
*/
|
|
if ((ind = pattern_match_equivalence
|
|
(m->word, ind, mt, addc)) != CHR_INVALID) {
|
|
if (ret) {
|
|
if (imeta(ind)) {
|
|
*p++ = Meta;
|
|
*p++ = ind ^ 32;
|
|
} else
|
|
*p++ = ind;
|
|
} else
|
|
len += imeta(ind) ? 2 : 1;
|
|
}
|
|
}
|
|
if (ret)
|
|
*p++ = ']';
|
|
else
|
|
len++;
|
|
} else {
|
|
int newlen, addadd;
|
|
|
|
switch (m->word->tp) {
|
|
case CPAT_NCLASS:
|
|
/*
|
|
* TODO: the old logic implies that we need to
|
|
* match *add, i.e. it should be deleted from
|
|
* the set of character's we're not allowed to
|
|
* match. That's too much like hard work for
|
|
* now. Indeed, in general it's impossible
|
|
* without trickery. Consider *add == 'A',
|
|
* range == "[^[:upper:]]": we would have to
|
|
* resort to something like "(A|[^[:upper:]])";
|
|
* and in an expression like that *add may or
|
|
* may not need backslashing. So we're deep
|
|
* into see-if-we-can-get-away-without
|
|
* territory.
|
|
*/
|
|
if (ret) {
|
|
*p++ = '[';
|
|
*p++ = '^';
|
|
} else
|
|
len += 2;
|
|
/*
|
|
* Convert the compiled range string back
|
|
* to an ordinary string.
|
|
*/
|
|
newlen =
|
|
pattern_range_to_string(m->word->u.str, p);
|
|
DPUTS(!newlen, "empty character range");
|
|
if (ret) {
|
|
p += newlen;
|
|
*p++ = ']';
|
|
} else
|
|
len += newlen + 1;
|
|
break;
|
|
|
|
case CPAT_CCLASS:
|
|
/*
|
|
* If there is an equivalence only on one
|
|
* side it's not equivalent to anything.
|
|
* Treat it as an ordinary character class.
|
|
*/
|
|
case CPAT_EQUIV:
|
|
case CPAT_CHAR:
|
|
if (ret)
|
|
*p++ = '[';
|
|
else
|
|
len++;
|
|
/*
|
|
* We needed to add *add specially only if
|
|
* it is not covered by the range. This
|
|
* is necessary for correct syntax---consider
|
|
* if *add is ] and ] is also the first
|
|
* character in the range.
|
|
*/
|
|
addadd = !pattern_match1(m->word, addc, &mt);
|
|
if (addadd && *add == ']') {
|
|
if (ret)
|
|
*p++ = *add;
|
|
else
|
|
len++;
|
|
}
|
|
if (m->word->tp == CPAT_CHAR) {
|
|
/*
|
|
* The matcher just matches a single
|
|
* character, but we need to be able
|
|
* to match *add, too, hence we do
|
|
* this as a [...].
|
|
*/
|
|
if (ret) {
|
|
if (imeta(m->word->u.chr)) {
|
|
*p++ = Meta;
|
|
*p++ = m->word->u.chr ^ 32;
|
|
} else
|
|
*p++ = m->word->u.chr;
|
|
} else
|
|
len += imeta(m->word->u.chr) ? 2 : 1;
|
|
} else {
|
|
/*
|
|
* Convert the compiled range string back
|
|
* to an ordinary string.
|
|
*/
|
|
newlen =
|
|
pattern_range_to_string(m->word->u.str, p);
|
|
DPUTS(!newlen, "empty character range");
|
|
if (ret)
|
|
p += newlen;
|
|
else
|
|
len += newlen;
|
|
}
|
|
if (addadd && *add != ']') {
|
|
if (ret) {
|
|
memcpy(p, add, addlen);
|
|
p += addlen;
|
|
} else
|
|
len += addlen;
|
|
}
|
|
if (ret)
|
|
*p++ = ']';
|
|
else
|
|
len++;
|
|
break;
|
|
|
|
case CPAT_ANY:
|
|
if (ret)
|
|
*p++ = '?';
|
|
else
|
|
len++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
add += addlen;
|
|
mp++;
|
|
}
|
|
if (ret) {
|
|
*p = '\0';
|
|
return ret;
|
|
}
|
|
p = ret = zhalloc(len + 1);
|
|
add = adds;
|
|
}
|
|
}
|
|
|
|
|
|
static char *
|
|
cfp_matcher_pats(char *matcher, char *add)
|
|
{
|
|
Cmatcher m = parse_cmatcher(NULL, matcher);
|
|
|
|
if (m && m != pcm_err) {
|
|
char *tmp;
|
|
int al = strlen(add), zl = ztrlen(add), tl, cl;
|
|
VARARR(Cmatcher, ms, zl);
|
|
Cmatcher *mp;
|
|
Cpattern stopp;
|
|
int stopl = 0;
|
|
|
|
memset(ms, 0, zl * sizeof(Cmatcher));
|
|
|
|
for (; m && *add; m = m->next) {
|
|
stopp = NULL;
|
|
if (!(m->flags & (CMF_LEFT|CMF_RIGHT))) {
|
|
if (m->llen == 1 && m->wlen == 1) {
|
|
for (tmp = add, tl = al, mp = ms; tl; ) {
|
|
if (pattern_match(m->line, tmp, NULL, NULL)) {
|
|
if (*mp) {
|
|
*tmp = '\0';
|
|
al = tmp - add;
|
|
break;
|
|
} else
|
|
*mp = m;
|
|
}
|
|
cl = (*tmp == Meta) ? 2 : 1;
|
|
tl -= cl;
|
|
tmp += cl;
|
|
mp += cl;
|
|
}
|
|
} else {
|
|
stopp = m->line;
|
|
stopl = m->llen;
|
|
}
|
|
} else if (m->flags & CMF_RIGHT) {
|
|
if (m->wlen < 0 && !m->llen && m->ralen == 1) {
|
|
for (tmp = add, tl = al, mp = ms; tl; ) {
|
|
if (pattern_match(m->right, tmp, NULL, NULL)) {
|
|
if (*mp || (tmp == add && *tmp == '.')) {
|
|
*tmp = '\0';
|
|
al = tmp - add;
|
|
break;
|
|
} else
|
|
*mp = m;
|
|
}
|
|
cl = (*tmp == Meta) ? 2 : 1;
|
|
tl -= cl;
|
|
tmp += cl;
|
|
mp += cl;
|
|
}
|
|
} else if (m->llen) {
|
|
stopp = m->line;
|
|
stopl = m->llen;
|
|
} else {
|
|
stopp = m->right;
|
|
stopl = m->ralen;
|
|
}
|
|
} else {
|
|
if (!m->lalen)
|
|
return "";
|
|
|
|
stopp = m->left;
|
|
stopl = m->lalen;
|
|
}
|
|
if (stopp)
|
|
for (tmp = add, tl = al; tl >= stopl; ) {
|
|
if (pattern_match(stopp, tmp, NULL, NULL)) {
|
|
*tmp = '\0';
|
|
al = tmp - add;
|
|
break;
|
|
}
|
|
cl = (*tmp == Meta) ? 2 : 1;
|
|
tl -= cl;
|
|
tmp += cl;
|
|
}
|
|
}
|
|
if (*add)
|
|
return cfp_matcher_range(ms, add);
|
|
}
|
|
return add;
|
|
}
|
|
|
|
static void
|
|
cfp_opt_pats(char **pats, char *matcher)
|
|
{
|
|
char *add, **p, *q, *t, *s;
|
|
|
|
if (!compprefix || !*compprefix)
|
|
return;
|
|
|
|
if (comppatmatch && *comppatmatch) {
|
|
tokenize(t = rembslash(dyncat(compprefix, compsuffix)));
|
|
remnulargs(t);
|
|
if (haswilds(t))
|
|
return;
|
|
}
|
|
add = (char *) zhalloc(strlen(compprefix) * 2 + 1);
|
|
for (s = compprefix, t = add; *s; s++) {
|
|
if (*s != '\\' || !s[1] || s[1] == '*' || s[1] == '?' ||
|
|
s[1] == '<' || s[1] == '>' || s[1] == '(' || s[1] == ')' ||
|
|
s[1] == '[' || s[1] == ']' || s[1] == '|' || s[1] == '#' ||
|
|
s[1] == '^' || s[1] == '~' || s[1] == '=') {
|
|
if ((s == compprefix || s[-1] != '\\') &&
|
|
(*s == '*' || *s == '?' || *s == '<' || *s == '>' ||
|
|
*s == '(' || *s == ')' || *s == '[' || *s == ']' ||
|
|
*s == '|' || *s == '#' || *s == '^' || *s == '~' ||
|
|
*s == '='))
|
|
*t++ = '\\';
|
|
*t++ = *s;
|
|
}
|
|
}
|
|
*t = '\0';
|
|
for (p = pats; *add && (q = *p); p++) {
|
|
if (*q) {
|
|
q = dupstring(q);
|
|
t = q + strlen(q) - 1;
|
|
if (*t == ')') {
|
|
for (s = t--; t > q; t--)
|
|
if (*t == ')' || *t == '|' || *t == '~' || *t == '(')
|
|
break;
|
|
if (t != q && *t == '(')
|
|
*t = '\0';
|
|
}
|
|
for (; *q && *add; q++) {
|
|
if (*q == '\\' && q[1]) {
|
|
for (s = add, q++; *s && *s != *q; s++);
|
|
*s = '\0';
|
|
} else if (*q == '<') {
|
|
for (s = add; *s && !idigit(*s); s++);
|
|
*s = '\0';
|
|
} else if (*q == '[') {
|
|
int not;
|
|
char *x = ++q;
|
|
|
|
if ((not = (*x == '!' || *x == '^')))
|
|
x++;
|
|
for (; *x; x++) {
|
|
if (x[1] == '-' && x[2]) {
|
|
char c1 = *x, c2 = x[2];
|
|
|
|
for (s = add; *s && (*x < c1 || *x > c2); s++);
|
|
*s = '\0';
|
|
} else {
|
|
for (s = add; *s && *s != *x; s++);
|
|
*s = '\0';
|
|
}
|
|
}
|
|
} else if (*q != '?' && *q != '*' && *q != '(' && *q != ')' &&
|
|
*q != '|' && *q != '~' && *q != '#') {
|
|
for (s = add; *s && *s != *q; s++);
|
|
*s = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (*add) {
|
|
if (*matcher && !(add = cfp_matcher_pats(matcher, add)))
|
|
return;
|
|
|
|
for (p = pats; *p; p++)
|
|
if (**p == '*')
|
|
*p = dyncat(add, *p);
|
|
}
|
|
}
|
|
|
|
static LinkList
|
|
cfp_bld_pats(UNUSED(int dirs), LinkList names, char *skipped, char **pats)
|
|
{
|
|
LinkList ret = newlinklist();
|
|
LinkNode node;
|
|
int ol, sl = strlen(skipped), pl, dot;
|
|
char **p, *o, *str;
|
|
|
|
dot = (unset(GLOBDOTS) && compprefix && *compprefix == '.');
|
|
for (node = firstnode(names); node; incnode(node)) {
|
|
ol = strlen(o = (char *) getdata(node));
|
|
for (p = pats; *p; p++) {
|
|
pl = strlen(*p);
|
|
str = (char *) zhalloc(ol + sl + pl + 1);
|
|
strcpy(str, o);
|
|
strcpy(str + ol, skipped);
|
|
strcpy(str + ol + sl, *p);
|
|
addlinknode(ret, str);
|
|
if (dot && **p != '.') {
|
|
str = (char *) zhalloc(ol + sl + pl + 2);
|
|
strcpy(str, o);
|
|
strcpy(str + ol, skipped);
|
|
str[ol + sl] = '.';
|
|
strcpy(str + ol + sl + 1, *p);
|
|
addlinknode(ret, str);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static LinkList
|
|
cfp_add_sdirs(LinkList final, LinkList orig, char *skipped,
|
|
char *sdirs, char **fake)
|
|
{
|
|
int add = 0;
|
|
|
|
if (*sdirs && (isset(GLOBDOTS) || (compprefix && *compprefix == '.'))) {
|
|
if (!strcmp(sdirs, "yes") || !strcmp(sdirs, "true") ||
|
|
!strcmp(sdirs, "on") || !strcmp(sdirs, "1"))
|
|
add = 2;
|
|
else if (!strcmp(sdirs, ".."))
|
|
add = 1;
|
|
}
|
|
if (add) {
|
|
LinkNode node;
|
|
char *s1 = dyncat(skipped, "..");
|
|
char *s2 = (add == 2 ? dyncat(skipped, ".") : NULL), *m;
|
|
|
|
for (node = firstnode(orig); node; incnode(node)) {
|
|
if ((m = (char *) getdata(node))) {
|
|
addlinknode(final, dyncat(m, s1));
|
|
if (s2)
|
|
addlinknode(final, dyncat(m, s2));
|
|
}
|
|
}
|
|
}
|
|
if (fake && *fake) {
|
|
LinkNode node;
|
|
char *m, *f, *p, *t, *a, c;
|
|
int sl = strlen(skipped) + 1;
|
|
struct stat st1, st2;
|
|
Patprog pprog;
|
|
|
|
for (; (f = *fake); fake++) {
|
|
f = dupstring(f);
|
|
for (p = t = f; *p; p++) {
|
|
if (*p == ':')
|
|
break;
|
|
else if (*p == '\\' && p[1] == ':') {
|
|
/*
|
|
* strip quoted colons here; rely
|
|
* on tokenization to strip other backslashes
|
|
*/
|
|
p++;
|
|
}
|
|
*t++ = *p;
|
|
}
|
|
if (*p) {
|
|
*t = *p++ = '\0';
|
|
if (!*p)
|
|
continue;
|
|
|
|
tokenize(f);
|
|
pprog = patcompile(f, PAT_STATIC, NULL);
|
|
untokenize(f);
|
|
for (node = firstnode(orig); node; incnode(node)) {
|
|
if ((m = (char *) getdata(node)) &&
|
|
((pprog ? pattry(pprog, m) : !strcmp(f, m)) ||
|
|
(!stat(f, &st1) && !stat((*m ? m : "."), &st2) &&
|
|
st1.st_dev == st2.st_dev &&
|
|
st1.st_ino == st2.st_ino))) {
|
|
while (*p) {
|
|
while (*p && inblank(*p))
|
|
p++;
|
|
if (!*p)
|
|
break;
|
|
for (f = t = p; *p; p++) {
|
|
if (inblank(*p))
|
|
break;
|
|
else if (*p == '\\' && p[1])
|
|
p++;
|
|
*t++ = *p;
|
|
}
|
|
c = *t;
|
|
*t = '\0';
|
|
a = (char *) zhalloc(strlen(m) + sl + strlen(f));
|
|
strcpy(a, m);
|
|
strcat(a, skipped);
|
|
strcat(a, f);
|
|
addlinknode(final, a);
|
|
*t = c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return final;
|
|
}
|
|
|
|
static LinkList
|
|
cf_pats(int dirs, int noopt, LinkList names, char **accept, char *skipped,
|
|
char *matcher, char *sdirs, char **fake, char **pats)
|
|
{
|
|
LinkList ret;
|
|
char *dpats[2];
|
|
|
|
if ((ret = cfp_test_exact(names, accept, skipped)))
|
|
return cfp_add_sdirs(ret, names, skipped, sdirs, fake);
|
|
|
|
if (dirs) {
|
|
dpats[0] = "*(-/)";
|
|
dpats[1] = NULL;
|
|
pats = dpats;
|
|
}
|
|
if (!noopt)
|
|
cfp_opt_pats(pats, matcher);
|
|
|
|
return cfp_add_sdirs(cfp_bld_pats(dirs, names, skipped, pats),
|
|
names, skipped, sdirs, fake);
|
|
}
|
|
|
|
/*
|
|
* This function looks at device/inode pairs to determine if
|
|
* a file is one we should ignore because of its relationship
|
|
* to the current or parent directory.
|
|
*
|
|
* We don't follow symbolic links here, because typically
|
|
* a user will not want an explicit link to the current or parent
|
|
* directory ignored.
|
|
*/
|
|
static void
|
|
cf_ignore(char **names, LinkList ign, char *style, char *path)
|
|
{
|
|
int pl = strlen(path), tpar, tpwd, found;
|
|
struct stat nst, est, st;
|
|
char *n, *c, *e;
|
|
|
|
tpar = !!strstr(style, "parent");
|
|
if ((tpwd = !!strstr(style, "pwd")) && lstat(pwd, &est))
|
|
tpwd = 0;
|
|
|
|
if (!tpar && !tpwd)
|
|
return;
|
|
|
|
for (; (n = *names); names++) {
|
|
if (!ztat(n, &nst, 1) && S_ISDIR(nst.st_mode)) {
|
|
if (tpwd && nst.st_dev == est.st_dev && nst.st_ino == est.st_ino) {
|
|
addlinknode(ign, quotestring(n, NULL, QT_BACKSLASH));
|
|
continue;
|
|
}
|
|
if (tpar && !strncmp((c = dupstring(n)), path, pl)) {
|
|
found = 0;
|
|
while ((e = strrchr(c, '/')) && e > c + pl) {
|
|
*e = '\0';
|
|
if (!ztat(c, &st, 0) &&
|
|
st.st_dev == nst.st_dev && st.st_ino == nst.st_ino) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (found || ((e = strrchr(c, '/')) && e > c + pl &&
|
|
!ztat(c, &st, 1) && st.st_dev == nst.st_dev &&
|
|
st.st_ino == nst.st_ino))
|
|
addlinknode(ign, quotestring(n, NULL, QT_BACKSLASH));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static LinkList
|
|
cf_remove_other(char **names, char *pre, int *amb)
|
|
{
|
|
char *p;
|
|
|
|
if ((p = strchr(pre, '/'))) {
|
|
char **n;
|
|
|
|
*p = '\0';
|
|
pre = dyncat(pre, "/");
|
|
*p = '/';
|
|
|
|
for (n = names; *n; n++)
|
|
if (strpfx(pre, *n))
|
|
break;
|
|
|
|
if (*n) {
|
|
LinkList ret = newlinklist();
|
|
|
|
for (; *names; names++)
|
|
if (strpfx(pre, *names))
|
|
addlinknode(ret, dupstring(*names));
|
|
|
|
*amb = 0;
|
|
|
|
return ret;
|
|
} else {
|
|
if (!(p = *names++))
|
|
*amb = 0;
|
|
else {
|
|
char *q;
|
|
|
|
if ((q = strchr((p = dupstring(p)), '/')))
|
|
*q = '\0';
|
|
|
|
p = dyncat(p, "/");
|
|
|
|
for (; *names; names++)
|
|
if (!strpfx(p, *names)) {
|
|
*amb = 1;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (!(p = *names++))
|
|
*amb = 0;
|
|
else
|
|
for (; *names; names++)
|
|
if (strcmp(p, *names)) {
|
|
*amb = 1;
|
|
return NULL;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
bin_compfiles(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
if (incompfunc != 1) {
|
|
zwarnnam(nam, "can only be called from completion function");
|
|
return 1;
|
|
}
|
|
if (**args != '-') {
|
|
zwarnnam(nam, "missing option: %s", *args);
|
|
return 1;
|
|
}
|
|
switch (args[0][1]) {
|
|
case 'p':
|
|
case 'P':
|
|
if (args[0][2] && (args[0][2] != '-' || args[0][3])) {
|
|
zwarnnam(nam, "invalid option: %s", *args);
|
|
return 1;
|
|
} else {
|
|
char **tmp;
|
|
LinkList l;
|
|
|
|
if (!args[1] || !args[2] || !args[3] || !args[4] || !args[5] ||
|
|
!args[6] || (args[0][1] == 'p' && !args[7])) {
|
|
zwarnnam(nam, "too few arguments");
|
|
return 1;
|
|
}
|
|
queue_signals();
|
|
if (!(tmp = getaparam(args[1]))) {
|
|
zwarnnam(nam, "unknown parameter: %s", args[1]);
|
|
return 0;
|
|
}
|
|
for (l = newlinklist(); *tmp; tmp++)
|
|
addlinknode(l, *tmp);
|
|
set_list_array(args[1], cf_pats((args[0][1] == 'P'), !!args[0][2],
|
|
l, getaparam(args[2]), args[3],
|
|
args[4], args[5],
|
|
getaparam(args[6]), args + 7));
|
|
unqueue_signals();
|
|
return 0;
|
|
}
|
|
case 'i':
|
|
if (args[0][2]) {
|
|
zwarnnam(nam, "invalid option: %s", *args);
|
|
return 1;
|
|
} else {
|
|
char **tmp;
|
|
LinkList l;
|
|
|
|
if (!args[1] || !args[2] || !args[3] || !args[4]) {
|
|
zwarnnam(nam, "too few arguments");
|
|
return 1;
|
|
}
|
|
if (args[5]) {
|
|
zwarnnam(nam, "too many arguments");
|
|
return 1;
|
|
}
|
|
queue_signals();
|
|
tmp = getaparam(args[2]);
|
|
l = newlinklist();
|
|
if (tmp)
|
|
for (; *tmp; tmp++)
|
|
addlinknode(l, *tmp);
|
|
if (!(tmp = getaparam(args[1]))) {
|
|
unqueue_signals();
|
|
zwarnnam(nam, "unknown parameter: %s", args[1]);
|
|
return 0;
|
|
}
|
|
cf_ignore(tmp, l, args[3], args[4]);
|
|
unqueue_signals();
|
|
set_list_array(args[2], l);
|
|
return 0;
|
|
}
|
|
case 'r':
|
|
{
|
|
char **tmp;
|
|
LinkList l;
|
|
int ret = 0;
|
|
|
|
if (!args[1] || !args[2]) {
|
|
zwarnnam(nam, "too few arguments");
|
|
return 1;
|
|
}
|
|
if (args[3]) {
|
|
zwarnnam(nam, "too many arguments");
|
|
return 1;
|
|
}
|
|
queue_signals();
|
|
if (!(tmp = getaparam(args[1]))) {
|
|
unqueue_signals();
|
|
zwarnnam(nam, "unknown parameter: %s", args[1]);
|
|
return 0;
|
|
}
|
|
if ((l = cf_remove_other(tmp, args[2], &ret)))
|
|
set_list_array(args[1], l);
|
|
unqueue_signals();
|
|
return ret;
|
|
}
|
|
}
|
|
zwarnnam(nam, "invalid option: %s", *args);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
bin_compgroups(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
|
|
{
|
|
Heap oldheap;
|
|
char *n;
|
|
|
|
if (incompfunc != 1) {
|
|
zwarnnam(nam, "can only be called from completion function");
|
|
return 1;
|
|
}
|
|
SWITCHHEAPS(oldheap, compheap) {
|
|
while ((n = *args++)) {
|
|
endcmgroup(NULL);
|
|
begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
|
|
endcmgroup(NULL);
|
|
begcmgroup(n, CGF_UNIQALL);
|
|
endcmgroup(NULL);
|
|
begcmgroup(n, CGF_NOSORT|CGF_UNIQCON);
|
|
endcmgroup(NULL);
|
|
begcmgroup(n, CGF_UNIQALL);
|
|
endcmgroup(NULL);
|
|
begcmgroup(n, CGF_NOSORT);
|
|
endcmgroup(NULL);
|
|
begcmgroup(n, 0);
|
|
}
|
|
} SWITCHBACKHEAPS(oldheap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct builtin bintab[] = {
|
|
BUILTIN("comparguments", 0, bin_comparguments, 1, -1, 0, NULL, NULL),
|
|
BUILTIN("compdescribe", 0, bin_compdescribe, 3, -1, 0, NULL, NULL),
|
|
BUILTIN("compfiles", 0, bin_compfiles, 1, -1, 0, NULL, NULL),
|
|
BUILTIN("compgroups", 0, bin_compgroups, 1, -1, 0, NULL, NULL),
|
|
BUILTIN("compquote", 0, bin_compquote, 1, -1, 0, "p", NULL),
|
|
BUILTIN("comptags", 0, bin_comptags, 1, -1, 0, NULL, NULL),
|
|
BUILTIN("comptry", 0, bin_comptry, 0, -1, 0, NULL, NULL),
|
|
BUILTIN("compvalues", 0, bin_compvalues, 1, -1, 0, NULL, NULL)
|
|
};
|
|
|
|
static struct features module_features = {
|
|
bintab, sizeof(bintab)/sizeof(*bintab),
|
|
NULL, 0,
|
|
NULL, 0,
|
|
NULL, 0,
|
|
0
|
|
};
|
|
|
|
|
|
/**/
|
|
int
|
|
setup_(UNUSED(Module m))
|
|
{
|
|
memset(cadef_cache, 0, sizeof(cadef_cache));
|
|
memset(cvdef_cache, 0, sizeof(cvdef_cache));
|
|
|
|
memset(comptags, 0, sizeof(comptags));
|
|
|
|
lasttaglevel = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
features_(Module m, char ***features)
|
|
{
|
|
*features = featuresarray(m, &module_features);
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
enables_(Module m, int **enables)
|
|
{
|
|
return handlefeatures(m, &module_features, enables);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
boot_(Module m)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
cleanup_(Module m)
|
|
{
|
|
return setfeatureenables(m, &module_features, NULL);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
finish_(UNUSED(Module m))
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_CACACHE; i++)
|
|
freecadef(cadef_cache[i]);
|
|
for (i = 0; i < MAX_CVCACHE; i++)
|
|
freecvdef(cvdef_cache[i]);
|
|
|
|
for (i = 0; i < MAX_TAGS; i++)
|
|
freectags(comptags[i]);
|
|
|
|
return 0;
|
|
}
|