mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-02-24 23:14:14 +00:00
734 lines
15 KiB
C
734 lines
15 KiB
C
/***********************************************************************
|
|
* *
|
|
* This software is part of the ast package *
|
|
* Copyright (c) 1982-2011 AT&T Intellectual Property *
|
|
* and is licensed under the *
|
|
* Eclipse Public License, Version 1.0 *
|
|
* by AT&T Intellectual Property *
|
|
* *
|
|
* A copy of the License is available at *
|
|
* http://www.eclipse.org/org/documents/epl-v10.html *
|
|
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
|
|
* *
|
|
* Information and Software Systems Research *
|
|
* AT&T Research *
|
|
* Florham Park NJ *
|
|
* *
|
|
* David Korn <dgk@research.att.com> *
|
|
* *
|
|
***********************************************************************/
|
|
#pragma prototyped
|
|
/*
|
|
* bash style history expansion
|
|
*
|
|
* Author:
|
|
* Karsten Fleischer
|
|
* Omnium Software Engineering
|
|
* An der Luisenburg 7
|
|
* D-51379 Leverkusen
|
|
* Germany
|
|
*
|
|
* <K.Fleischer@omnium.de>
|
|
*/
|
|
|
|
|
|
#include "defs.h"
|
|
#include "edit.h"
|
|
|
|
#if ! SHOPT_HISTEXPAND
|
|
|
|
NoN(hexpand)
|
|
|
|
#else
|
|
|
|
static char *modifiers = "htrepqxs&";
|
|
static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 };
|
|
|
|
#define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;}
|
|
|
|
struct subst
|
|
{
|
|
char *str[2]; /* [0] is "old", [1] is "new" string */
|
|
};
|
|
|
|
|
|
/*
|
|
* parse an /old/new/ string, delimiter expected as first char.
|
|
* if "old" not specified, keep sb->str[0]
|
|
* if "new" not specified, set sb->str[1] to empty string
|
|
* read up to third delimiter char, \n or \0, whichever comes first.
|
|
* return address is one past the last valid char in s:
|
|
* - the address containing \n or \0 or
|
|
* - one char beyond the third delimiter
|
|
*/
|
|
|
|
static char *parse_subst(const char *s, struct subst *sb)
|
|
{
|
|
char *cp,del;
|
|
int off,n = 0;
|
|
|
|
/* build the strings on the stack, mainly for '&' substitution in "new" */
|
|
off = staktell();
|
|
|
|
/* init "new" with empty string */
|
|
if(sb->str[1])
|
|
free(sb->str[1]);
|
|
sb->str[1] = strdup("");
|
|
|
|
/* get delimiter */
|
|
del = *s;
|
|
|
|
cp = (char*) s + 1;
|
|
|
|
while(n < 2)
|
|
{
|
|
if(*cp == del || *cp == '\n' || *cp == '\0')
|
|
{
|
|
/* delimiter or EOL */
|
|
if(staktell() != off)
|
|
{
|
|
/* dupe string on stack and rewind stack */
|
|
stakputc('\0');
|
|
if(sb->str[n])
|
|
free(sb->str[n]);
|
|
sb->str[n] = strdup(stakptr(off));
|
|
stakseek(off);
|
|
}
|
|
n++;
|
|
|
|
/* if not delimiter, we've reached EOL. Get outta here. */
|
|
if(*cp != del)
|
|
break;
|
|
}
|
|
else if(*cp == '\\')
|
|
{
|
|
if(*(cp+1) == del) /* quote delimiter */
|
|
{
|
|
stakputc(del);
|
|
cp++;
|
|
}
|
|
else if(*(cp+1) == '&' && n == 1)
|
|
{ /* quote '&' only in "new" */
|
|
stakputc('&');
|
|
cp++;
|
|
}
|
|
else
|
|
stakputc('\\');
|
|
}
|
|
else if(*cp == '&' && n == 1 && sb->str[0])
|
|
/* substitute '&' with "old" in "new" */
|
|
stakputs(sb->str[0]);
|
|
else
|
|
stakputc(*cp);
|
|
cp++;
|
|
}
|
|
|
|
/* rewind stack */
|
|
stakseek(off);
|
|
|
|
return cp;
|
|
}
|
|
|
|
/*
|
|
* history expansion main routine
|
|
*/
|
|
|
|
int hist_expand(const char *ln, char **xp)
|
|
{
|
|
int off, /* stack offset */
|
|
q, /* quotation flags */
|
|
p, /* flag */
|
|
c, /* current char */
|
|
flag=0; /* HIST_* flags */
|
|
Sfoff_t n, /* history line number, counter, etc. */
|
|
i, /* counter */
|
|
w[2]; /* word range */
|
|
char *sp, /* stack pointer */
|
|
*cp, /* current char in ln */
|
|
*str, /* search string */
|
|
*evp, /* event/word designator string, for error msgs */
|
|
*cc=0, /* copy of current line up to cp; temp ptr */
|
|
hc[3], /* default histchars */
|
|
*qc="\'\"`"; /* quote characters */
|
|
Sfio_t *ref=0, /* line referenced by event designator */
|
|
*tmp=0, /* temporary line buffer */
|
|
*tmp2=0;/* temporary line buffer */
|
|
Histloc_t hl; /* history location */
|
|
static Namval_t *np = 0; /* histchars variable */
|
|
static struct subst sb = {0,0}; /* substitution strings */
|
|
static Sfio_t *wm=0; /* word match from !?string? event designator */
|
|
|
|
if(!wm)
|
|
wm = sfopen(NULL, NULL, "swr");
|
|
|
|
hc[0] = '!';
|
|
hc[1] = '^';
|
|
hc[2] = 0;
|
|
if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np)))
|
|
{
|
|
if(cp[0])
|
|
{
|
|
hc[0] = cp[0];
|
|
if(cp[1])
|
|
{
|
|
hc[1] = cp[1];
|
|
if(cp[2])
|
|
hc[2] = cp[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* save shell stack */
|
|
if(off = staktell())
|
|
sp = stakfreeze(0);
|
|
|
|
cp = (char*)ln;
|
|
|
|
while(cp && *cp)
|
|
{
|
|
/* read until event/quick substitution/comment designator */
|
|
if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2])
|
|
|| (*cp == hc[1] && cp != ln))
|
|
{
|
|
if(*cp == '\\') /* skip escaped designators */
|
|
stakputc(*cp++);
|
|
else if(*cp == '\'') /* skip quoted designators */
|
|
{
|
|
do
|
|
stakputc(*cp);
|
|
while(*++cp && *cp != '\'');
|
|
}
|
|
stakputc(*cp++);
|
|
continue;
|
|
}
|
|
|
|
if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */
|
|
{
|
|
stakputc(*cp++);
|
|
stakputs(cp);
|
|
DONE();
|
|
}
|
|
|
|
n = -1;
|
|
str = 0;
|
|
flag &= HIST_EVENT; /* save event flag for returning later */
|
|
evp = cp;
|
|
ref = 0;
|
|
|
|
if(*cp == hc[1]) /* shortcut substitution */
|
|
{
|
|
flag |= HIST_QUICKSUBST;
|
|
goto getline;
|
|
}
|
|
|
|
if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */
|
|
{
|
|
cp += 2;
|
|
goto getline;
|
|
}
|
|
|
|
switch(c = *++cp) {
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
case '\0':
|
|
case '=':
|
|
case '(':
|
|
stakputc(hc[0]);
|
|
continue;
|
|
case '#': /* the line up to current position */
|
|
flag |= HIST_HASH;
|
|
cp++;
|
|
n = staktell(); /* terminate string and dup */
|
|
stakputc('\0');
|
|
cc = strdup(stakptr(0));
|
|
stakseek(n); /* remove null byte again */
|
|
ref = sfopen(ref, cc, "s"); /* open as file */
|
|
n = 0; /* skip history file referencing */
|
|
break;
|
|
case '-': /* back reference by number */
|
|
if(!isdigit(*(cp+1)))
|
|
goto string_event;
|
|
cp++;
|
|
case '0': /* reference by number */
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
n = 0;
|
|
while(isdigit(*cp))
|
|
n = n * 10 + (*cp++) - '0';
|
|
if(c == '-')
|
|
n = -n;
|
|
break;
|
|
case '$':
|
|
n = -1;
|
|
case ':':
|
|
break;
|
|
case '?':
|
|
cp++;
|
|
flag |= HIST_QUESTION;
|
|
string_event:
|
|
default:
|
|
/* read until end of string or word designator/modifier */
|
|
str = cp;
|
|
while(*cp)
|
|
{
|
|
cp++;
|
|
if((!(flag&HIST_QUESTION) &&
|
|
(*cp == ':' || isspace(*cp)
|
|
|| *cp == '^' || *cp == '$'
|
|
|| *cp == '*' || *cp == '-'
|
|
|| *cp == '%')
|
|
)
|
|
|| ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n')))
|
|
{
|
|
c = *cp;
|
|
*cp = '\0';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
getline:
|
|
flag |= HIST_EVENT;
|
|
if(str) /* !string or !?string? event designator */
|
|
{
|
|
|
|
/* search history for string */
|
|
hl = hist_find(shgd->hist_ptr, str,
|
|
shgd->hist_ptr->histind,
|
|
flag&HIST_QUESTION, -1);
|
|
if((n = hl.hist_command) == -1)
|
|
n = 0; /* not found */
|
|
}
|
|
if(n)
|
|
{
|
|
if(n < 0) /* determine index for backref */
|
|
n = shgd->hist_ptr->histind + n;
|
|
/* search and use history file if found */
|
|
if(n > 0 && hist_seek(shgd->hist_ptr, n) != -1)
|
|
ref = shgd->hist_ptr->histfp;
|
|
|
|
}
|
|
if(!ref)
|
|
{
|
|
/* string not found or command # out of range */
|
|
c = *cp;
|
|
*cp = '\0';
|
|
errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp);
|
|
*cp = c;
|
|
DONE();
|
|
}
|
|
|
|
if(str) /* string search: restore orig. line */
|
|
{
|
|
if(flag&HIST_QUESTION)
|
|
*cp++ = c; /* skip second question mark */
|
|
else
|
|
*cp = c;
|
|
}
|
|
|
|
/* colon introduces either word designators or modifiers */
|
|
if(*(evp = cp) == ':')
|
|
cp++;
|
|
|
|
w[0] = 0; /* -1 means last word, -2 means match from !?string? */
|
|
w[1] = -1; /* -1 means last word, -2 means suppress last word */
|
|
|
|
if(flag & HIST_QUICKSUBST) /* shortcut substitution */
|
|
goto getsel;
|
|
|
|
n = 0;
|
|
while(n < 2)
|
|
{
|
|
switch(c = *cp++) {
|
|
case '^': /* first word */
|
|
if(n == 0)
|
|
{
|
|
w[0] = w[1] = 1;
|
|
goto skip;
|
|
}
|
|
else
|
|
goto skip2;
|
|
case '$': /* last word */
|
|
w[n] = -1;
|
|
goto skip;
|
|
case '%': /* match from !?string? event designator */
|
|
if(n == 0)
|
|
{
|
|
if(!str)
|
|
{
|
|
w[0] = 0;
|
|
w[1] = -1;
|
|
ref = wm;
|
|
}
|
|
else
|
|
{
|
|
w[0] = -2;
|
|
w[1] = sftell(ref) + hl.hist_char;
|
|
}
|
|
sfseek(wm, 0, SEEK_SET);
|
|
goto skip;
|
|
}
|
|
default:
|
|
skip2:
|
|
cp--;
|
|
n = 2;
|
|
break;
|
|
case '*': /* until last word */
|
|
if(n == 0)
|
|
w[0] = 1;
|
|
w[1] = -1;
|
|
skip:
|
|
flag |= HIST_WORDDSGN;
|
|
n = 2;
|
|
break;
|
|
case '-': /* until last word or specified index */
|
|
w[1] = -2;
|
|
flag |= HIST_WORDDSGN;
|
|
n = 1;
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9': /* specify index */
|
|
if((*evp == ':') || w[1] == -2)
|
|
{
|
|
w[n] = c - '0';
|
|
while(isdigit(c=*cp++))
|
|
w[n] = w[n] * 10 + c - '0';
|
|
flag |= HIST_WORDDSGN;
|
|
if(n == 0)
|
|
w[1] = w[0];
|
|
n++;
|
|
}
|
|
else
|
|
n = 2;
|
|
cp--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(w[0] != -2 && w[1] > 0 && w[0] > w[1])
|
|
{
|
|
c = *cp;
|
|
*cp = '\0';
|
|
errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
|
|
*cp = c;
|
|
DONE();
|
|
}
|
|
|
|
/* no valid word designator after colon, rewind */
|
|
if(!(flag & HIST_WORDDSGN) && (*evp == ':'))
|
|
cp = evp;
|
|
|
|
getsel:
|
|
/* open temp buffer, let sfio do the (re)allocation */
|
|
tmp = sfopen(NULL, NULL, "swr");
|
|
|
|
/* push selected words into buffer, squash
|
|
whitespace into single blank or a newline */
|
|
n = i = q = 0;
|
|
|
|
while((c = sfgetc(ref)) > 0)
|
|
{
|
|
if(isspace(c))
|
|
{
|
|
flag |= (c == '\n' ? HIST_NEWLINE : 0);
|
|
continue;
|
|
}
|
|
|
|
if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1))
|
|
{
|
|
if(w[0] < 0)
|
|
sfseek(tmp, 0, SEEK_SET);
|
|
else
|
|
i = sftell(tmp);
|
|
|
|
if(i > 0)
|
|
sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' ');
|
|
|
|
flag &= ~HIST_NEWLINE;
|
|
p = 1;
|
|
}
|
|
else
|
|
p = 0;
|
|
|
|
do
|
|
{
|
|
cc = strchr(qc, c);
|
|
q ^= cc ? 1<<(int)(cc - qc) : 0;
|
|
if(p)
|
|
sfputc(tmp, c);
|
|
}
|
|
while((c = sfgetc(ref)) > 0 && (!isspace(c) || q));
|
|
|
|
if(w[0] == -2 && sftell(ref) > w[1])
|
|
break;
|
|
|
|
flag |= (c == '\n' ? HIST_NEWLINE : 0);
|
|
n++;
|
|
}
|
|
if(w[0] != -2 && w[1] >= 0 && w[1] >= n)
|
|
{
|
|
c = *cp;
|
|
*cp = '\0';
|
|
errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
|
|
*cp = c;
|
|
DONE();
|
|
}
|
|
else if(w[1] == -2) /* skip last word */
|
|
sfseek(tmp, i, SEEK_SET);
|
|
|
|
/* remove trailing newline */
|
|
if(sftell(tmp))
|
|
{
|
|
sfseek(tmp, -1, SEEK_CUR);
|
|
if(sfgetc(tmp) == '\n')
|
|
sfungetc(tmp, '\n');
|
|
}
|
|
|
|
sfputc(tmp, '\0');
|
|
|
|
if(str)
|
|
{
|
|
if(wm)
|
|
sfclose(wm);
|
|
wm = tmp;
|
|
}
|
|
|
|
if(cc && (flag&HIST_HASH))
|
|
{
|
|
/* close !# temp file */
|
|
sfclose(ref);
|
|
flag &= ~HIST_HASH;
|
|
free(cc);
|
|
cc = 0;
|
|
}
|
|
|
|
evp = cp;
|
|
|
|
/* selected line/words are now in buffer, now go for the modifiers */
|
|
while(*cp == ':' || (flag & HIST_QUICKSUBST))
|
|
{
|
|
if(flag & HIST_QUICKSUBST)
|
|
{
|
|
flag &= ~HIST_QUICKSUBST;
|
|
c = 's';
|
|
cp--;
|
|
}
|
|
else
|
|
c = *++cp;
|
|
|
|
sfseek(tmp, 0, SEEK_SET);
|
|
tmp2 = sfopen(tmp2, NULL, "swr");
|
|
|
|
if(c == 'g') /* global substitution */
|
|
{
|
|
flag |= HIST_GLOBALSUBST;
|
|
c = *++cp;
|
|
}
|
|
|
|
if(cc = strchr(modifiers, c))
|
|
flag |= mod_flags[cc - modifiers];
|
|
else
|
|
{
|
|
errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c);
|
|
DONE();
|
|
}
|
|
|
|
if(c == 'h' || c == 'r') /* head or base */
|
|
{
|
|
n = -1;
|
|
while((c = sfgetc(tmp)) > 0)
|
|
{ /* remember position of / or . */
|
|
if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r'))
|
|
n = sftell(tmp2);
|
|
sfputc(tmp2, c);
|
|
}
|
|
if(n > 0)
|
|
{ /* rewind to last / or . */
|
|
sfseek(tmp2, n, SEEK_SET);
|
|
/* end string there */
|
|
sfputc(tmp2, '\0');
|
|
}
|
|
}
|
|
else if(c == 't' || c == 'e') /* tail or suffix */
|
|
{
|
|
n = 0;
|
|
while((c = sfgetc(tmp)) > 0)
|
|
{ /* remember position of / or . */
|
|
if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e'))
|
|
n = sftell(tmp);
|
|
}
|
|
/* rewind to last / or . */
|
|
sfseek(tmp, n, SEEK_SET);
|
|
/* copy from there on */
|
|
while((c = sfgetc(tmp)) > 0)
|
|
sfputc(tmp2, c);
|
|
}
|
|
else if(c == 's' || c == '&')
|
|
{
|
|
cp++;
|
|
|
|
if(c == 's')
|
|
{
|
|
/* preset old with match from !?string? */
|
|
if(!sb.str[0] && wm)
|
|
sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0));
|
|
cp = parse_subst(cp, &sb);
|
|
}
|
|
|
|
if(!sb.str[0] || !sb.str[1])
|
|
{
|
|
c = *cp;
|
|
*cp = '\0';
|
|
errormsg(SH_DICT, ERROR_ERROR,
|
|
"%s%s: no previous substitution",
|
|
(flag & HIST_QUICKSUBST) ? ":s" : "",
|
|
evp);
|
|
*cp = c;
|
|
DONE();
|
|
}
|
|
|
|
/* need pointer for strstr() */
|
|
str = sfsetbuf(tmp, (Void_t*)1, 0);
|
|
|
|
flag |= HIST_SUBSTITUTE;
|
|
while(flag & HIST_SUBSTITUTE)
|
|
{
|
|
/* find string */
|
|
if(cc = strstr(str, sb.str[0]))
|
|
{ /* replace it */
|
|
c = *cc;
|
|
*cc = '\0';
|
|
sfputr(tmp2, str, -1);
|
|
sfputr(tmp2, sb.str[1], -1);
|
|
*cc = c;
|
|
str = cc + strlen(sb.str[0]);
|
|
}
|
|
else if(!sftell(tmp2))
|
|
{ /* not successful */
|
|
c = *cp;
|
|
*cp = '\0';
|
|
errormsg(SH_DICT, ERROR_ERROR,
|
|
"%s%s: substitution failed",
|
|
(flag & HIST_QUICKSUBST) ? ":s" : "",
|
|
evp);
|
|
*cp = c;
|
|
DONE();
|
|
}
|
|
/* loop if g modifier specified */
|
|
if(!cc || !(flag & HIST_GLOBALSUBST))
|
|
flag &= ~HIST_SUBSTITUTE;
|
|
}
|
|
/* output rest of line */
|
|
sfputr(tmp2, str, -1);
|
|
if(*cp)
|
|
cp--;
|
|
}
|
|
|
|
if(sftell(tmp2))
|
|
{ /* if any substitutions done, swap buffers */
|
|
if(wm != tmp)
|
|
sfclose(tmp);
|
|
tmp = tmp2;
|
|
tmp2 = 0;
|
|
}
|
|
cc = 0;
|
|
if(*cp)
|
|
cp++;
|
|
}
|
|
|
|
/* flush temporary buffer to stack */
|
|
if(tmp)
|
|
{
|
|
sfseek(tmp, 0, SEEK_SET);
|
|
|
|
if(flag & HIST_QUOTE)
|
|
stakputc('\'');
|
|
|
|
while((c = sfgetc(tmp)) > 0)
|
|
{
|
|
if(isspace(c))
|
|
{
|
|
flag = flag & ~HIST_NEWLINE;
|
|
|
|
/* squash white space to either a
|
|
blank or a newline */
|
|
do
|
|
flag |= (c == '\n' ? HIST_NEWLINE : 0);
|
|
while((c = sfgetc(tmp)) > 0 && isspace(c));
|
|
|
|
sfungetc(tmp, c);
|
|
|
|
c = (flag & HIST_NEWLINE) ? '\n' : ' ';
|
|
|
|
if(flag & HIST_QUOTE_BR)
|
|
{
|
|
stakputc('\'');
|
|
stakputc(c);
|
|
stakputc('\'');
|
|
}
|
|
else
|
|
stakputc(c);
|
|
}
|
|
else if((c == '\'') && (flag & HIST_QUOTE))
|
|
{
|
|
stakputc('\'');
|
|
stakputc('\\');
|
|
stakputc(c);
|
|
stakputc('\'');
|
|
}
|
|
else
|
|
stakputc(c);
|
|
}
|
|
if(flag & HIST_QUOTE)
|
|
stakputc('\'');
|
|
}
|
|
}
|
|
|
|
stakputc('\0');
|
|
|
|
done:
|
|
if(cc && (flag&HIST_HASH))
|
|
{
|
|
/* close !# temp file */
|
|
sfclose(ref);
|
|
free(cc);
|
|
cc = 0;
|
|
}
|
|
|
|
/* error? */
|
|
if(staktell() && !(flag & HIST_ERROR))
|
|
*xp = strdup(stakfreeze(1));
|
|
|
|
/* restore shell stack */
|
|
if(off)
|
|
stakset(sp,off);
|
|
else
|
|
stakseek(0);
|
|
|
|
/* drop temporary files */
|
|
|
|
if(tmp && tmp != wm)
|
|
sfclose(tmp);
|
|
if(tmp2)
|
|
sfclose(tmp2);
|
|
|
|
return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK);
|
|
}
|
|
|
|
#endif
|