getflags: import from 4e, with usage

fixes #6 http://bitbucket.org/rsc/plan9port/issue/6/

http://codereview.appspot.com/95043
This commit is contained in:
Russ Cox 2009-07-15 02:48:37 -04:00
parent 9bea9069bf
commit 3cd77ae679
7 changed files with 231 additions and 485 deletions

82
cmd/getflags.c Normal file
View file

@ -0,0 +1,82 @@
#include <u.h>
#include <libc.h>
void
usage(void)
{
print("status=usage\n");
exits(0);
}
char*
findarg(char *flags, Rune r)
{
char *p;
Rune rr;
for(p=flags; p!=(char*)1; p=strchr(p, ',')+1){
chartorune(&rr, p);
if(rr == r)
return p;
}
return nil;
}
int
countargs(char *p)
{
int n;
n = 1;
while(*p == ' ')
p++;
for(; *p && *p != ','; p++)
if(*p == ' ' && *(p-1) != ' ')
n++;
return n;
}
void
main(int argc, char *argv[])
{
char *flags, *p, buf[512];
int i, n;
Fmt fmt;
quotefmtinstall();
argv0 = argv[0]; /* for sysfatal */
flags = getenv("flagfmt");
if(flags == nil){
fprint(2, "$flagfmt not set\n");
print("exit 'missing flagfmt'");
exits(0);
}
fmtfdinit(&fmt, 1, buf, sizeof buf);
for(p=flags; p!=(char*)1; p=strchr(p, ',')+1)
fmtprint(&fmt, "flag%.1s=()\n", p);
ARGBEGIN{
default:
if((p = findarg(flags, ARGC())) == nil)
usage();
p += runelen(ARGC());
if(*p == ',' || *p == 0){
fmtprint(&fmt, "flag%C=1\n", ARGC());
break;
}
n = countargs(p);
fmtprint(&fmt, "flag%C=(", ARGC());
for(i=0; i<n; i++)
fmtprint(&fmt, "%s%q", i ? " " : "", EARGF(usage()));
fmtprint(&fmt, ")\n");
}ARGEND
fmtprint(&fmt, "*=(");
for(i=0; i<argc; i++)
fmtprint(&fmt, "%s%q", i ? " " : "", argv[i]);
fmtprint(&fmt, ")\n");
fmtprint(&fmt, "status=''\n");
fmtfdflush(&fmt);
exits(0);
}

72
cmd/usage.c Normal file
View file

@ -0,0 +1,72 @@
#include <u.h>
#include <libc.h>
void
main(int argc, char **argv)
{
Fmt fmt;
char buf[512];
char *argv0, *args, *flags, *p, *p0;
int single;
Rune r;
argv0 = getenv("0");
if(argv0 == nil) {
if(argc > 1)
argv0 = argv[1];
else
argv0 = "unknown-program-name";
}
if((p = strrchr(argv0, '/')) != nil)
argv0 = p+1;
flags = getenv("flagfmt");
args = getenv("args");
if(argv0 == nil){
fprint(2, "aux/usage: $0 not set\n");
exits("$0");
}
if(flags == nil)
flags = "";
if(args == nil)
args = "";
fmtfdinit(&fmt, 2, buf, sizeof buf);
fmtprint(&fmt, "usage: %s", argv0);
if(flags[0]){
single = 0;
for(p=flags; *p; ){
p += chartorune(&r, p);
if(*p == ',' || *p == 0){
if(!single){
fmtprint(&fmt, " [-");
single = 1;
}
fmtprint(&fmt, "%C", r);
if(*p == ',')
p++;
continue;
}
while(*p == ' ')
p++;
if(single){
fmtprint(&fmt, "]");
single = 0;
}
p0 = p;
p = strchr(p0, ',');
if(p == nil)
p = "";
else
*p++ = 0;
fmtprint(&fmt, " [-%C %s]", r, p0);
}
if(single)
fmtprint(&fmt, "]");
}
if(args)
fmtprint(&fmt, " %s", args);
fmtprint(&fmt, "\n");
fmtfdflush(&fmt);
exits("usage");
}

77
man8/getflags.8 Normal file
View file

@ -0,0 +1,77 @@
.TH GETFLAGS 8
.SH NAME
getflags, usage \- command-line parsing for shell scripts
.SH SYNOPSIS
.B getflags $*
.PP
.B usage [ progname ]
.SH DESCRIPTION
.I Getflags
parses the options in its command-line arguments
according to the environment variable
.BR $flagfmt .
This variable should be a list of comma-separated options.
Each option can be a single letter, indicating that it does
not take arguments, or a letter followed by the space-separated
names of its arguments.
.I Getflags
prints an
.IR rc (1)
script on standard output which initializes the
environment variable
.BI $flag x
for every option mentioned in
.BR $flagfmt .
If the option is not present on the command-line, the script
sets that option's flag variable to an empty list.
Otherwise, the script sets that option's flag variable with
a list containing the option's arguments or,
if the option takes no arguments,
with the string
.BR 1 .
The script also sets the variable
.B $*
to the list of arguments following the options.
The final line in the script sets the
.B $status
variable, to the empty string on success
and to the string
.B usage
when there is an error parsing the command line.
.PP
.I Usage
prints a usage message to standard error.
It creates the message using
.BR $flagfmt ,
as described above,
.BR $args ,
which should contain the string to be printed explaining
non-option arguments,
and
.BR $0 ,
the program name
(see
.IR rc (1)).
If run under
.IR sh (1),
which does not set
.BR $0 ,
the program name must be given explicitly on the command line.
.SH EXAMPLE
Parse the arguments for
.IR leak (1):
.IP
.EX
flagfmt='b,s,f binary,r res,x width'
args='name | pid list'
if(! ifs=() eval `{getflags $*} || ~ $#* 0){
usage
exit usage
}
.EE
.SH SOURCE
.B \*9/src/cmd/getflags.c
.br
.B \*9/src/cmd/usage.c
.SH SEE ALSO
.IR arg (3)

View file

@ -1,267 +0,0 @@
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include "getflags.h"
char **flag[NFLAG];
char cmdline[NCMDLINE+1];
char *cmdname;
char *flagset[];
char *flagset[]={"<flag>"};
static char *flagarg="";
static void reverse(char **, char **);
static int scanflag(int, char *);
static int reason;
#define RESET 1
#define ARGCCOUNT 2
#define FLAGSYN 3
#define BADFLAG 4
static int badflag;
char *getflagsargv[NGETFLAGSARGV+2]; /* original argv stored here for people who need it */
int
getflags(int argc, char *argv[], char *flags)
{
char *s, *t;
int i, j, c, count;
flagarg=flags;
if(cmdname==0){
cmdname=argv[0];
for(i=0;i!=argc && i!=NGETFLAGSARGV;i++) getflagsargv[i]=argv[i];
if(argc>NGETFLAGSARGV) getflagsargv[i++]="...";
getflagsargv[i]=0;
}
s=cmdline;
for(i=0;i!=argc;i++){
for(t=argv[i];*t;)
if(s!=&cmdline[NCMDLINE])
*s++=*t++;
else
break;
if(i!=argc-1 && s!=&cmdline[NCMDLINE])
*s++=' ';
}
*s='\0';
i=1;
while(i!=argc && argv[i][0]=='-'){
s=argv[i]+1;
if(*s=='\0'){ /* if argument is "-", stop scanning and delete it */
for(j=i+1;j<=argc;j++)
argv[j-1]=argv[j];
return argc-1;
}
while(*s){
c=*s++;
count=scanflag(c, flags);
if(count==-1) return -1;
if(flag[c]){ reason=RESET; badflag=c; return -1; }
if(count==0){
flag[c]=flagset;
if(*s=='\0'){
for(j=i+1;j<=argc;j++)
argv[j-1]=argv[j];
--argc;
}
}
else{
if(*s=='\0'){
for(j=i+1;j<=argc;j++)
argv[j-1]=argv[j];
--argc;
s=argv[i];
}
if(argc-i<count){
reason=ARGCCOUNT;
badflag=c;
return -1;
}
reverse(argv+i, argv+argc);
reverse(argv+i, argv+argc-count);
reverse(argv+argc-count+1, argv+argc);
argc-=count;
flag[c]=argv+argc+1;
flag[c][0]=s;
s="";
}
}
}
return argc;
}
void
static reverse(char **p, char **q)
{
register char *t;
for(;p<q;p++,--q){ t=*p; *p=*q; *q=t; }
}
static int
scanflag(int c, char *f)
{
int fc, count;
if(0<=c && c<NFLAG) while(*f){
if(*f==' '){
f++;
continue;
}
fc=*f++;
if(*f==':'){
f++;
if(!isdigit((uchar)*f)){ reason=FLAGSYN; return -1; }
count=strtol(f, &f, 10);
}
else
count=0;
if(*f=='['){
int depth=1;
do{
f++;
if(*f=='\0'){ reason=FLAGSYN; return -1; }
if(*f=='[') depth++;
if(*f==']') depth--;
}while(depth>0);
f++;
}
if(c==fc) return count;
}
reason=BADFLAG;
badflag=c;
return -1;
}
static void errn(char *, int), errs(char *), errc(int);
void
usage(char *tail)
{
char *s, *t, c;
int count, nflag=0;
switch(reason){
case RESET:
errs("Flag -");
errc(badflag);
errs(": set twice\n");
break;
case ARGCCOUNT:
errs("Flag -");
errc(badflag);
errs(": too few arguments\n");
break;
case FLAGSYN:
errs("Bad argument to getflags!\n");
break;
case BADFLAG:
errs("Illegal flag -");
errc(badflag);
errc('\n');
break;
}
errs("Usage: ");
errs(cmdname);
for(s=flagarg;*s;){
c=*s;
if(*s++==' ') continue;
if(*s==':'){
s++;
count=strtol(s, &s, 10);
}
else count=0;
if(count==0){
if(nflag==0) errs(" [-");
nflag++;
errc(c);
}
if(*s=='['){
int depth=1;
s++;
for(;*s!='\0' && depth>0; s++)
if (*s==']') depth--;
else if (*s=='[') depth++;
}
}
if(nflag) errs("]");
for(s=flagarg;*s;){
c=*s;
if(*s++==' ') continue;
if(*s==':'){
s++;
count=strtol(s, &s, 10);
}
else count=0;
if(count!=0){
errs(" [-");
errc(c);
if(*s=='['){
int depth=1;
s++;
t=s;
for(;*s!='\0' && depth>0; s++)
if (*s==']') depth--;
else if (*s=='[') depth++;
errs(" ");
errn(t, s-t);
}
else
while(count--) errs(" arg");
errs("]");
}
else if(*s=='['){
int depth=1;
s++;
for(;*s!='\0' && depth>0; s++)
if (*s==']') depth--;
else if (*s=='[') depth++;
}
}
if(tail){
errs(" ");
errs(tail);
}
errs("\n");
exits("usage");
}
static void
errn(char *s, int count)
{
while(count){ errc(*s++); --count; }
}
static void
errs(char *s)
{
while(*s) errc(*s++);
}
#define NBUF 80
static char buf[NBUF], *bufp=buf;
static void
errc(int c){
*bufp++=c;
if(bufp==&buf[NBUF] || c=='\n'){
write(2, buf, bufp-buf);
bufp=buf;
}
}
#ifdef TEST
#include <stdio.h>
main(int argc, char *argv[])
{
int c, i, n;
if(argc<3){
fprint(2, "Usage: %s flags cmd ...\n", argv[0]);
exits("usage");
}
n=getflags(argc-2, argv+2, argv[1]);
if(n<0) usage("...");
putchar('\n');
for(c=0;c!=128;c++) if(flag[c]){
print("\t-.%c. ", c);
n=scanflag(c, argv[1]);
for(i=0;i!=n;i++) print(" <%s>", flag[c][i]);
putchar('\n');
}
}
#endif

View file

@ -1,199 +0,0 @@
/*% cyntax % && cc -go # %
* getflags: process flags for command files
* Usage: ifs='' eval `{getflags [-s] flagfmt [arg ...]} # rc
* Usage: IFS= eval `getflags -b [-s] flagfmt [arg...]` # Bourne shell
* -b means give Bourne-shell compatible output
*/
#include <u.h>
#include <libc.h>
#include "getflags.h"
/* predefine functions */
void bourneprint(int, char *[]);
void bournearg(char *);
void rcprint(int, char *[]);
void usmsg(char *);
int count(int, char *);
void rcarg(char *);
void
main(int argc, char *argv[])
{
int bourne;
argc=getflags(argc, argv, "b");
if(argc<2) usage("flagfmt [arg ...]");
bourne=flag['b']!=0;
flag['b']=0;
if((argc=getflags(argc-1, argv+1, argv[1]))<0){
usmsg(argv[1]);
exits(0);
}
if(bourne) bourneprint(argc, argv);
else rcprint(argc, argv);
exits(0);
}
void
bourneprint(int argc, char *argv[])
{
register int c, i, n;
for(c=0;c!=NFLAG;c++) if(flag[c]){
print("FLAG%c=", c); /* bug -- c could be a bad char */
n=count(c, argv[1]);
if(n==0)
print("1\n");
else{
print("'");
bournearg(flag[c][0]);
for(i=1;i!=n;i++){
print(" ");
bournearg(flag[c][i]);
}
print("'\n");
}
}
print("set --");
for(c=1;c!=argc;c++){
print(" ");
bournearg(argv[c+1]);
}
print("\n");
}
void
bournearg(char *s)
{
for(;*s;s++)
if(*s=='\'')
print("'\\''");
else
print("%c", *s);
}
void
rcprint(int argc, char *argv[])
{
int c, i, n;
for(c=0;c!=NFLAG;c++) if(flag[c]){
print("FLAG%c=", c); /* bug -- c could be a bad char */
n=count(c, argv[1]);
if(n==0)
print("''");
else if(n==1)
rcarg(flag[c][0]);
else{
print("(");
rcarg(flag[c][0]);
for(i=1;i!=n;i++){
print(" ");
rcarg(flag[c][i]);
}
print(")");
}
print("\n");
}
print("*=");
if(argc==1) print("()");
else if(argc==2) rcarg(argv[2]);
else{
print("(");
rcarg(argv[2]);
for(c=2;c!=argc;c++){
print(" ");
rcarg(argv[c+1]);
}
print(")");
}
print("\n");
}
void
usmsg(char *flagarg)
{
char *s, *t, c;
int count, nflag=0;
print("echo Usage: $0'");
for(s=flagarg;*s;){
c=*s;
if(*s++==' ') continue;
if(*s==':')
count = strtol(s+1, &s, 10);
else count=0;
if(count==0){
if(nflag==0) print(" [-");
nflag++;
print("%c", c);
}
if(*s=='['){
int depth=1;
s++;
for(;*s!='\0' && depth>0; s++)
if (*s==']') depth--;
else if (*s=='[') depth++;
}
}
if(nflag) print("]");
for(s=flagarg;*s;){
c=*s;
if(*s++==' ') continue;
if(*s==':')
count = strtol(s+1, &s, 10);
else count=0;
if(count!=0){
print(" [-");
print("%c", c);
if(*s=='['){
int depth=1;
s++;
t=s;
for(;*s!='\0' && depth>0; s++)
if (*s==']') depth--;
else if (*s=='[') depth++;
print(" ");
write(1, t, s - t);
}
else
while(count--) print(" arg");
print("]");
}
else if(*s=='['){
int depth=1;
s++;
for(;*s!='\0' && depth>0; s++)
if (*s==']') depth--;
else if (*s=='[') depth++;
}
}
print("' $usage;\n");
print("exit 'usage'\n");
}
int
count(int flag, char *flagarg)
{
char *s, c;
int n;
for(s=flagarg;*s;){
c=*s;
if(*s++==' ') continue;
if(*s==':')
n = strtol(s+1, &s, 10);
else n=0;
if(*s=='['){
int depth=1;
s++;
for(;*s!='\0' && depth>0; s++)
if (*s==']') depth--;
else if (*s=='[') depth++;
}
if(c==flag) return n;
}
return -1; /* never happens */
}
void
rcarg(char *s)
{
if(*s=='\0' || strpbrk(s, "\n \t#;&|^$=`'{}()<>?")){
print("\'");
for(;*s;s++)
if(*s=='\'') print("''");
else print("%c", *s);
print("\'");
}
else print("%s", s);
}

View file

@ -1,10 +0,0 @@
#define NFLAG 128
#define NCMDLINE 512
#define NGETFLAGSARGV 256
extern char **flag[NFLAG];
extern char cmdline[NCMDLINE+1];
extern char *cmdname;
extern char *flagset[];
extern char *getflagsargv[NGETFLAGSARGV+2];
int getflags(int, char *[], char *);
void usage(char *);

View file

@ -1,9 +0,0 @@
<$PLAN9/src/mkhdr
TARG=getflags
OFILES=\
getflags.$O\
funcgetflags.$O\
<$PLAN9/src/mkone