Skip to content

Commit 7115310

Browse files
committed
allow combining literals and regex/dyn items on the same level
1 parent f08a569 commit 7115310

3 files changed

Lines changed: 110 additions & 49 deletions

File tree

yacli.c

Lines changed: 100 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// $Id: yacli.c,v 4.11 2025/03/09 22:18:34 bbonev Exp $
1+
// $Id: yacli.c,v 4.12 2026/04/29 23:03:05 bbonev Exp $
22
//
33
// Copyright © 2015-2020 Boian Bonev (bbonev@ipacct.com) {{{
44
//
@@ -122,7 +122,8 @@ struct _yacli {
122122
history *hst_p; // pointer in history, NULL when not in history
123123
yascreen *s; // screen used to render output
124124
void (*cmdcb)(yacli *cli,const char *cmd,int code); // callback for each executed command
125-
void (*listcb)(yacli *cli,void *ctx,int code); // callback for getting dynamic list items
125+
void (*listcb)(yacli *cli,void *ctx,int code); // legacy: dynamic list callback (no prefix hint)
126+
void (*listcb2)(yacli *cli,void *ctx,int code,const char *prefix); // dynamic list callback with prefix hint for fast-skip
126127
void (*parsedcb)(yacli *cli,int ac,char **cmd); // user callback for matching command
127128
void (*ctrlzcb)(yacli *cli); // user callback to notify ctrl-z
128129
yacli_in_state state; // input bytestream DFA state
@@ -213,7 +214,7 @@ inline void yacli_set_showtermsize(yacli *cli,int v) { // {{{
213214
cli->showtsize=!!v;
214215
} // }}}
215216

216-
static char myver[]="\0Yet another command line interface library (https://github.com/bbonev/yacli) $Revision: 4.11 $\n\n"; // {{{
217+
static char myver[]="\0Yet another command line interface library (https://github.com/bbonev/yacli) $Revision: 4.12 $\n\n"; // {{{
217218
// }}}
218219

219220
inline const char *yacli_ver(void) { // {{{
@@ -1715,6 +1716,14 @@ inline void yacli_set_list_cb(yacli *cli,void (*listcb)(yacli *cli,void *ctx,int
17151716
if (!cli)
17161717
return;
17171718
cli->listcb=listcb;
1719+
cli->listcb2=NULL;
1720+
} // }}}
1721+
1722+
inline void yacli_set_list_cb2(yacli *cli,void (*listcb2)(yacli *cli,void *ctx,int code,const char *prefix)) { // {{{
1723+
if (!cli)
1724+
return;
1725+
cli->listcb2=listcb2;
1726+
cli->listcb=NULL;
17181727
} // }}}
17191728

17201729
inline void yacli_set_cmd_cb(yacli *cli,void (*cmdcb)(yacli *cli,const char *cmd,int code)) { // {{{
@@ -1762,15 +1771,15 @@ static inline void yacli_dyn_vacuum(cmnode *n) { // {{{
17621771
yacli_dyn_vacuum(n->child);
17631772
} // }}}
17641773

1765-
static void yacli_dyn_upd(yacli *cli,cmnode *n) { // {{{
1774+
static void yacli_dyn_upd(yacli *cli,cmnode *n,const char *prefix) { // {{{
17661775
cmnode *pnode;
17671776

17681777
if (!cli)
17691778
return;
17701779

17711780
if (!n)
17721781
return;
1773-
if (!cli->listcb) // no way to update
1782+
if (!cli->listcb&&!cli->listcb2) // no way to update
17741783
return;
17751784
if (n->cmd[0]!='@') // not a dynamic node
17761785
return;
@@ -1780,7 +1789,10 @@ static void yacli_dyn_upd(yacli *cli,cmnode *n) { // {{{
17801789
n->dyn=NULL;
17811790
}
17821791
pnode=n;
1783-
cli->listcb(cli,pnode,atoi(n->cmd+1));
1792+
if (cli->listcb2)
1793+
cli->listcb2(cli,pnode,atoi(n->cmd+1),prefix?prefix:"");
1794+
else
1795+
cli->listcb(cli,pnode,atoi(n->cmd+1));
17841796
} // }}}
17851797

17861798
static inline int yacli_cmd_dump_node(yacli *cli,cmnode *n) { // {{{
@@ -1793,7 +1805,7 @@ static inline int yacli_cmd_dump_node(yacli *cli,cmnode *n) { // {{{
17931805
cmnode *p;
17941806
int first=1;
17951807

1796-
yacli_dyn_upd(cli,n);
1808+
yacli_dyn_upd(cli,n,"");
17971809
p=n->dyn;
17981810
if (!p)
17991811
return 1;
@@ -1980,7 +1992,7 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
19801992
int cmp;
19811993

19821994
if (cn->cmd[0]=='@') { // dynamic command
1983-
yacli_dyn_upd(cli,cn);
1995+
yacli_dyn_upd(cli,cn,word);
19841996
dyn=1;
19851997
cn=cn->dyn;
19861998
if (!cn)
@@ -2027,9 +2039,18 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
20272039
break;
20282040
} else if (cmp<0) { // check for single/multiple possibilities
20292041
int isprefix=strncmp(word,cn->cmd,strlen(word))==0&&strlen(word)<strlen(cn->cmd);
2030-
int nxprefix=cn->next&&strncmp(word,cn->next->cmd,strlen(word))==0&&strlen(word)<strlen(cn->next->cmd);
2042+
int nxprefix=cn->next&&cn->next->cmd[0]!='^'&&cn->next->cmd[0]!='@'&&strncmp(word,cn->next->cmd,strlen(word))==0&&strlen(word)<strlen(cn->next->cmd);
20312043

2032-
if (!isprefix) {
2044+
if (!isprefix) { // word sorts before this literal and isn't a prefix; fall through to regex/dyn tail siblings, if any
2045+
cmnode *t=cn->next;
2046+
2047+
while (t&&t->cmd[0]!='^'&&t->cmd[0]!='@') // skip remaining literals (all sort even later, can't match)
2048+
t=t->next;
2049+
if (t) {
2050+
cn=t;
2051+
lastcn=cn;
2052+
continue;
2053+
}
20332054
yacli_print_nof(cli,"\nNo matched command (2)\n");
20342055
cli->redraw=1;
20352056
free(fb);
@@ -2080,7 +2101,7 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
20802101
completex=0;
20812102
cli->parsedcb=NULL;
20822103

2083-
while (t) {
2104+
while (t&&t->cmd[0]!='^'&&t->cmd[0]!='@') { // ignore regex/dyn tail siblings during literal partial-complete scan
20842105
int lisprefix=strncmp(word,t->cmd,strlen(word))==0&&strlen(word)<strlen(cn->cmd);
20852106

20862107
if (lisprefix)
@@ -2334,29 +2355,35 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
23342355
if (cn) // if we didn't hit leaf in tree, go one node up
23352356
lastcn=lastcn->parent;
23362357

2337-
p=lastcn->child;
2338-
if (p&&p->cmd[0]=='@') {
2339-
yacli_dyn_upd(cli,p);
2340-
dyn=1;
2341-
p=p->dyn;
2342-
}
2343-
23442358
// calculate max len of printed stuff
23452359
if (lastcn->isdyn?lastcn->parent->cb:lastcn->cb) // print self, if valid alone
23462360
maxcmdlen=strlen("<cr>");
2347-
while (p) { // print children
2348-
maxcmdlen=mymax(maxcmdlen,yacli_cmd_help_len(p->cmd,p->help,!!p->cb));
2361+
p=lastcn->child;
2362+
while (p) { // walk children, expanding any @-dynamic node inline
2363+
if (p->cmd[0]=='@') {
2364+
cmnode *d;
2365+
2366+
yacli_dyn_upd(cli,p,"");
2367+
dyn=1;
2368+
for (d=p->dyn;d;d=d->next)
2369+
maxcmdlen=mymax(maxcmdlen,yacli_cmd_help_len(d->cmd,d->isdyn?d->parent->help:d->help,!!d->cb));
2370+
} else
2371+
maxcmdlen=mymax(maxcmdlen,yacli_cmd_help_len(p->cmd,p->help,!!p->cb));
23492372
p=p->next;
23502373
}
23512374

23522375
// print in columns based on calculated max len
23532376
if (lastcn->isdyn?lastcn->parent->cb:lastcn->cb)
23542377
yacli_cmd_help_pr(cli,"",lastcn->isdyn?lastcn->parent->help:lastcn->help,1,maxcmdlen);
23552378
p=lastcn->child;
2356-
if (p&&p->cmd[0]=='@')
2357-
p=p->dyn;
23582379
while (p) {
2359-
yacli_cmd_help_pr(cli,p->cmd,p->isdyn?p->parent->help:p->help,!!p->cb,maxcmdlen);
2380+
if (p->cmd[0]=='@') {
2381+
cmnode *d;
2382+
2383+
for (d=p->dyn;d;d=d->next)
2384+
yacli_cmd_help_pr(cli,d->cmd,d->isdyn?d->parent->help:d->help,!!d->cb,maxcmdlen);
2385+
} else
2386+
yacli_cmd_help_pr(cli,p->cmd,p->isdyn?p->parent->help:p->help,!!p->cb,maxcmdlen);
23602387
p=p->next;
23612388
}
23622389
}
@@ -2383,29 +2410,50 @@ static inline int yacli_trycomplete(yacli *cli,int docomplete) { // {{{
23832410
else
23842411
if (cn&&alonematch) // last word was executable alone, so we have dived one more level, pop it up
23852412
lastcn=lastcn->parent;
2386-
p=lastcn;
2387-
if (p&&p->cmd[0]=='@') {
2388-
yacli_dyn_upd(cli,p);
2389-
dyn=1;
2390-
p=p->dyn;
2391-
}
23922413
// calculate max len of printed stuff
2414+
p=lastcn;
23932415
while (p) {
2394-
int isprefix=strncmp(lastword,p->cmd,strlen(lastword))==0&&strlen(lastword)<=strlen(p->cmd);
2416+
if (p->cmd[0]=='@') {
2417+
cmnode *d;
2418+
2419+
yacli_dyn_upd(cli,p,lastword);
2420+
dyn=1;
2421+
for (d=p->dyn;d;d=d->next) {
2422+
int isprefix=strncmp(lastword,d->cmd,strlen(lastword))==0&&strlen(lastword)<=strlen(d->cmd);
2423+
2424+
if (isprefix)
2425+
maxcmdlen=mymax(maxcmdlen,yacli_cmd_help_len(d->cmd,d->isdyn?d->parent->help:d->help,!!d->cb));
2426+
}
2427+
} else if (p->cmd[0]=='^') // regex placeholder always offered as a hint
2428+
maxcmdlen=mymax(maxcmdlen,yacli_cmd_help_len(p->cmd,p->help,!!p->cb));
2429+
else {
2430+
int isprefix=strncmp(lastword,p->cmd,strlen(lastword))==0&&strlen(lastword)<=strlen(p->cmd);
23952431

2396-
if (isprefix)
2397-
maxcmdlen=mymax(maxcmdlen,strlen(p->cmd)+(p->cb?5:0));
2432+
if (isprefix)
2433+
maxcmdlen=mymax(maxcmdlen,yacli_cmd_help_len(p->cmd,p->help,!!p->cb));
2434+
}
23982435
p=p->next;
23992436
}
24002437
// print in columns based on calculated max len
24012438
p=lastcn;
2402-
if (p&&p->cmd[0]=='@')
2403-
p=p->dyn;
24042439
while (p) {
2405-
int isprefix=strncmp(lastword,p->cmd,strlen(lastword))==0&&strlen(lastword)<=strlen(p->cmd);
2440+
if (p->cmd[0]=='@') {
2441+
cmnode *d;
24062442

2407-
if (isprefix)
2408-
yacli_cmd_help_pr(cli,p->cmd,p->isdyn?p->parent->help:p->help,!!p->cb,maxcmdlen);
2443+
for (d=p->dyn;d;d=d->next) {
2444+
int isprefix=strncmp(lastword,d->cmd,strlen(lastword))==0&&strlen(lastword)<=strlen(d->cmd);
2445+
2446+
if (isprefix)
2447+
yacli_cmd_help_pr(cli,d->cmd,d->isdyn?d->parent->help:d->help,!!d->cb,maxcmdlen);
2448+
}
2449+
} else if (p->cmd[0]=='^')
2450+
yacli_cmd_help_pr(cli,p->cmd,p->help,!!p->cb,maxcmdlen);
2451+
else {
2452+
int isprefix=strncmp(lastword,p->cmd,strlen(lastword))==0&&strlen(lastword)<=strlen(p->cmd);
2453+
2454+
if (isprefix)
2455+
yacli_cmd_help_pr(cli,p->cmd,p->isdyn?p->parent->help:p->help,!!p->cb,maxcmdlen);
2456+
}
24092457
p=p->next;
24102458
}
24112459
}
@@ -2914,19 +2962,23 @@ inline void *yacli_add_cmd(yacli *cli,void *parent,const char *cmd,const char *h
29142962
else
29152963
place=&cli->cmdt;
29162964

2917-
if ((cmd[0]=='^'||cmd[0]=='@')&&*place) // cannot combine dynamic/regex and static commands
2918-
return NULL;
2919-
if (*place&&((*place)->cmd[0]=='^'||(*place)->cmd[0]=='@'))
2920-
return NULL;
2965+
if (cmd[0]=='^'||cmd[0]=='@') { // regex/dynamic always tail; preserve insertion order among themselves
2966+
while (*place)
2967+
place=&(*place)->next;
2968+
} else { // literals: sorted; stop before any regex/dynamic siblings
2969+
while (*place) {
2970+
int cmp;
29212971

2922-
while (*place) {
2923-
int cmp=strcmp(cmd,(*place)->cmd);
2972+
if ((*place)->cmd[0]=='^'||(*place)->cmd[0]=='@') // reached the regex/dyn tail; insert before it
2973+
break;
2974+
cmp=strcmp(cmd,(*place)->cmd);
29242975

2925-
if (cmp==0) // duplicate command
2926-
return NULL;
2927-
if (cmp<0) // found proper place
2928-
break;
2929-
place=&(*place)->next;
2976+
if (cmp==0) // duplicate command
2977+
return NULL;
2978+
if (cmp<0) // found proper place
2979+
break;
2980+
place=&(*place)->next;
2981+
}
29302982
}
29312983

29322984
t=calloc(1,sizeof *t);

yacli.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// $Id: yacli.h,v 1.33 2025/03/09 22:17:27 bbonev Exp $
1+
// $Id: yacli.h,v 1.34 2026/04/29 23:03:06 bbonev Exp $
22
//
33
// Copyright © 2015-2020 Boian Bonev (bbonev@ipacct.com) {{{
44
//
@@ -92,6 +92,10 @@ inline void *yacli_add_cmd(yacli *cli,void *parent,const char *cmd,const char *h
9292
inline void yacli_list(yacli *cli,void *ctx,const char *item);
9393
// set list callback function
9494
inline void yacli_set_list_cb(yacli *cli,void (*list_cb)(yacli *cli,void *ctx,int code));
95+
// set list callback function (prefix-aware variant); prefix is the partial word being completed/matched ("" for full enumeration)
96+
// callbacks may use the prefix to fast-skip non-matching items; results need not be exhaustive when prefix is non-empty
97+
// either yacli_set_list_cb or yacli_set_list_cb2 is active at a time; the most recent setter wins
98+
inline void yacli_set_list_cb2(yacli *cli,void (*list_cb2)(yacli *cli,void *ctx,int code,const char *prefix));
9599
// set command callback function
96100
inline void yacli_set_cmd_cb(yacli *cli,void (*cmd_cb)(yacli *cli,const char *cmd,int code));
97101
// set ctrl-z callback function

yacli.vers

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ YACLI_4.04 {
3838
yacli_get_hint_i;
3939
local: *;
4040
};
41+
42+
YACLI_4.12 {
43+
global:
44+
yacli_set_list_cb2;
45+
} YACLI_4.04;

0 commit comments

Comments
 (0)