#include "gpsystem.h"
#include <string.h>
#include <stdlib.h> // exit()
#include <stdio.h>
#include <stdarg.h>
void gp_checkarg(const char*arg, const char*opts) {
if (!arg) return; // another problem
if (arg[0]!='-') return; // cannot be a long option
if (arg[1]!='-') return; // cannot be a long option
if (arg[2]) {
const char*a = arg+2;
const char*e = strchr(a,'='); // possible end of long option
if (!e) e = a+strlen(a); // end of long option
size_t la = e-a; // length of given tag, 1 = minimum
for (;*opts;opts+=strlen(opts)+1) {
const char*q0 = opts+1; // long option tag
const char*qe = strpbrk(q0,"\1\2"); // possible end of tag
if (!qe) qe = q0+strlen(q0); // end of tag
size_t lo = qe-q0; // length of tag to compare, can be 0!
if (la==lo && !memcmp(a,q0,la)) goto silly;
}
}else{
silly:
fprintf(stderr, "Error: This option may not be parameter of another option: \"%s\"\n",arg);
exit(1);
}
}
// This "getopt" function uses a callback for every found option,
// and uses a compact double-zero-terminated string for control.
void gp_getopt(const char*const*argv,const char*opts,void(*cb)(void*,char,const char*),void*cd) {
bool parsestop = false; // after "-" or "--", end parsing for options, callback with plain argument
const char*argv0 = *argv; // keep program name for error messages
while (*++argv) { // The argv[] array is zero terminated by C/C++ standard
const char*arg = *argv;
if (parsestop) cb(cd,0,arg);
else if (arg[0]=='-') {
if (arg[1]==arg[0]) {
if (!arg[2]) parsestop = true;
else{ // scan for known long option
const char*a = arg+2;
const char*a2 = strchr(a,'='); // allow "=" between tag and value
const char*e = a2 ? a2 : a+strlen(a);
size_t la = e-a; // length of given tag, 1 = minimum
for(const char*q=opts;*q;q+=strlen(q)+1) {
const char*q0 = q+1; // skip short option for now
const char*q1 = strpbrk(q0,"\1\2"); // scan for "argument" terminator
const char*qe = q1 ? q1 : q0+strlen(q0);
size_t lo = qe-q0; // length of tag to compare, can be 0!
if (la==lo && !memcmp(a,q0,la)) { // correct long option, look for argument
const char*b = 0;
if (q1) if (*q1==1) {
b = a2 ? a2+1 : *++argv; // unconditionally take next argument, even if 0
if (!b) fprintf(stderr,"%s: option '%s' requires an argument\n",argv0,arg);
}else{
if (a2) b = a2+1;
else if (argv[1] && argv[1][0]!='-') b = *++argv; // take next arg if not looking like an option
}
cb(cd,*q,b); // call back (in any case)
if (!*argv) return; // break-out when next argument is nullptr
goto found;
}
}
fprintf(stderr,"%s: unrecognized option '%s'\n",argv0,arg);
cb(cd,-1,arg); // take argument as (possibly wrong) file name?
found:;
}
}else if (!arg[1]) parsestop = true;
// process concatenated short options
else for (const char*a = arg;*++a;) {
const char*b = a[1]=='=' ? a+2 : a[1] ? a+1 : 0;
for(const char*q=opts;*q;q+=strlen(q)+1) {
if (*a==*q) {
const char*q1 = strpbrk(q+1,"\1\2");
if (q1) {
if (*q1==1) { // required argument?
if (!b) b = *++argv; // if no tail, take next arg
if (!b) fprintf(stderr,"%s: option requires an argument -- %c\n",argv0,*a);
}else{ // optional argument?
if (!b) { // if no tail, check next arg
b = argv[1] && argv[1][0]!='-' ? *++argv : 0;
}
}
}else b = 0; // Always no argument
goto found2; // exit inner loop with known option
}
}
fprintf(stderr,"%s: invalid option -- %c\n",argv0,*a);
found2:
cb(cd,*a,b);
if (b) break; // exit outer loop when argument was taken
// As side effect, unknown options are reported with argument, e.g.
// "-?=1" goes to callback with c == '?' and arg == "1".
// "-?" (same as "-h") c == '?' and arg == nullptr
// whereas "=" forces non-nullptr argument:
// "-?=" c == '?' and arg == ""
// however known 'h' produces:
// "-h==" c == 'h' and arg == nullptr
// and then
// c == '=' and arg == "" (for the second '=')
}
}else cb(cd,0,arg); // non-option argument (file name etc.)
}
}
// This functor-like class cuts long strings into words and checks whether it will fit
// onto the screen.
// Moreover, it emits TAB characters and take the current position into account;
// tab width is fixed to 8.
// UTF-8 non-combining characters are measured correctly. Assume monospaced font.
struct PrintW{
int left,width;
mutable int x;
char last;
void tab(int) const;
const PrintW&operator()(int x) const {tab(x);return*this;}
void put(char c) const {if (c) {putchar(c); x+=measure(c);}}
const PrintW&operator<<(char c) const {put(c);return*this;}
void put(const char*s, size_t slen) const {if (slen) do put(*s++); while(--slen);}
void put(const char*s) const {while (*s) put(*s++);}
const PrintW&operator<<(const char*s) const {put(s);return*this;}
int measure(char c) const;
static int measure(const char*s,size_t slen);
void operator()(const char*s);
void put_option(const char*arg,bool optional);
PrintW(int l,int w):left(l),width(w),x(0),last(0) {}
};
void PrintW::tab(int col) const{
while (x+7<col) put('\t');
while (x<col) put(' ');
}
int PrintW::measure(char c) const{
if (c < -0x40) return 0; // don't count UTF-8 trailing bytes
if (!c) return 0; // don't count "no character"
if (c=='\t') return 8-(x&7); // spaces to next tabstop
if (c=='\n') return -x; // let rewind to zero
return 1; // one character
}
// For simplicity, skip 0x80..0xBF character codes, will work with most UTF-8
int PrintW::measure(const char*s, size_t slen) {
int len=0;
if (slen) do if (*s++ >= -0x40) ++len; while(--slen);
return len;
}
void PrintW::operator()(const char*s) {
int indent=0;
while (*s) {
const char*eow = strpbrk(s,"\n\t "); // only these three characters are allowed!
if (!eow) eow = s+strlen(s);
int len = measure(s,eow-s);
if (x+measure(last)+len>width) {
put('\n');
tab(left+indent);
}else{
put(last);
if (last=='\n') { // the first '\n' in text switches the indent to 3 as a bullet list is expected to follow
tab(left);
indent=3;
}
}
put(s,eow-s);
if ((last=*(s=eow))) ++s;
}
put('\n');
}
void PrintW::put_option(const char*arg,bool optional) {
if (!arg || !*arg) return;
put(' ');
if (optional) put('['); // show bracket for optional argument
put(arg);
if (optional) put(']');
}
// This generic usage printout function uses the same opts as gp_getopt()
// and writes them out using localizable desc string list.
// Important: Options and descriptions must fit one-by-one.
// <left> must be less than <width> and both non-negative!
// TODO: Summary line (always the same?)
// TODO: Auto-detect screen width
// TODO: UTF-8 to OEMCP conversion for Windows (for non-english description)
// TODO: Use string resource for localized description (Windows only)
void _cdecl gp_usage(const char*argv0,const char*opts,const char*desc,
int left,int width,const char*footer,...) {
if (argv0) printf("Usage: %s [options] file\n",argv0);
printf("Options: [defaults in brackets after descriptions]\n");
PrintW pr(left+2,width); // this class avoids need for C++11 lambdas
for(;*opts;opts+=strlen(opts)+1, desc+=strlen(desc)+1) {
const char*q0=opts+1; // long option (if any)
const char*q1=strpbrk(q0,"\1\2"); // required (1) / optional (2) argument
const char*arg=0; // argument name?
if (q1) arg=q1+1; // Here is the name
else q1=q0+strlen(q0); // no (0) argument
if (*opts>' ') { // Have short option?
pr(2)<<'-'<<*opts; // show it
pr.put_option(arg,*q1==2);
if (*q0>' ') pr<<',';
pr<<' '; // TODO: Nicht ausgeben wenn TAB folgen kann
}
if (*q0>' ') {
pr(6);
pr<<'-'<<'-';
pr.put(q0,q1-q0);
pr.put_option(arg,*q1==2);
pr<<' '<<' ';
}
if (pr.x>pr.left+8) pr<<'\n';
pr(left);
pr(desc);
}
if (footer) {
va_list va;
va_start(va,footer);
vprintf(footer,va);
va_end(va);
}
printf("\tReport bugs to: %s\n",BUGREPORT);
}
Detected encoding: ASCII (7 bit) | 2
|