consolidate some arg processing

Unix and Windows had diverged significantly for command line
options handling.

This:
   1. uses the the Unix processing as a baseline.
   2. consolidates the code in earlyarg.c, where it can
      be a common copy to be shared.
   3. start converting the Windows command line argument
      processing to the Unix code that now resides in earlyarg.c.
This commit is contained in:
nhmall
2026-04-04 13:44:23 -04:00
parent 9c8c17b70c
commit 92340a6827
8 changed files with 480 additions and 402 deletions

View File

@@ -1907,9 +1907,9 @@ assure_syscf_file(void)
#else
fd = open(SYSCF_FILE, O_RDONLY);
#endif
#else
#else /* VMS */
fd = open(SYSCF_FILE, O_RDONLY, 0);
#endif
#endif /* VMS */
if (fd >= 0) {
/* readable */
close(fd);

View File

@@ -3,11 +3,22 @@
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#include "dlb.h"
staticfn void debug_fields(char *);
#ifndef NODUMPENUMS
staticfn void dump_enums(void);
#endif
ATTRNORETURN staticfn void opt_terminate(void) NORETURN;
ATTRNORETURN staticfn void opt_usage(const char *) NORETURN;
ATTRNORETURN staticfn void scores_only(int, char **, const char *) NORETURN;
staticfn char *lopt(char *, int, const char *, const char *, int *, char ***);
staticfn void consume_arg(int, int *, char ***);
staticfn void consume_two_args(int, int *, char ***);
#ifdef UNIX
extern boolean whoami(void);
#endif
/*
* Argument processing helpers - for xxmain() to share
@@ -40,9 +51,383 @@ static const struct early_opt earlyopts[] = {
#endif
};
#ifdef WIN32
extern int windows_early_options(const char *);
static char ArgVal_novalue[] = "[nothing]"; /* note: not 'const' */
enum cmdlinearg {
ArgValRequired = 0,
ArgValOptional = 1,
ArgValDisallowed = 2,
ArgVal_mask = (1 | 2),
ArgNamOneLetter = 4,
ArgNam_mask = 4,
ArgErrSilent = 0,
ArgErrComplain = 8,
ArgErr_mask = 8
};
/* approximate 'getopt_long()' for one option; all the comments refer to
"-windowtype" but the code isn't specific to that */
staticfn char *
lopt(char *arg, /* command line token; beginning matches 'optname' */
int lflags, /* cmdlinearg | errorhandling */
const char *optname, /* option's name; "-windowtype" in examples below */
const char *origarg, /* 'arg' might have had a dash prefix removed */
int *argc_p, /* argc that can have changes passed to caller */
char ***argv_p) /* argv[] ditto */
{
int argc = *argc_p;
char **argv = *argv_p;
char *p, *nextarg = (argc > 1 && argv[1][0] != '-') ? argv[1] : 0;
int l, opttype = (lflags & ArgVal_mask);
boolean oneletterok = ((lflags & ArgNam_mask) == ArgNamOneLetter),
complain = ((lflags & ArgErr_mask) == ArgErrComplain);
/* first letter must match */
if (arg[1] != optname[1]) {
loptbail:
if (complain)
config_error_add("Unknown option: %.60s", origarg);
return (char *) 0;
loptnotallowed:
if (complain)
config_error_add("Value not allowed: %.60s", origarg);
return (char *) 0;
loptrequired:
if (complain)
config_error_add("Missing required value: %.60s", origarg);
return (char *) 0;
}
if ((p = strchr(arg, '=')) == 0)
p = strchr(arg, ':');
if (p && opttype == ArgValDisallowed)
goto loptnotallowed;
l = (int) (p ? (long) (p - arg) : (long) strlen(arg));
if ((l > 2 || oneletterok) && !strncmp(arg, optname, l)) {
/* "-windowtype[=foo]" */
if (p)
++p; /* past '=' or ':' */
else if (opttype == ArgValRequired)
p = eos(arg); /* we have "-w[indowtype]" w/o "=foo"
* so we'll take foo from next element */
else
return ArgVal_novalue;
} else if (oneletterok) {
/* "-w..." but not "-w[indowtype[=foo]]" */
if (!p) {
p = &arg[2]; /* past 'w' of "-wfoo" */
#if 0 /* -x:value could work but is not supported (callers don't expect it) \
*/
} else if (p == arg + 2) {
++p; /* past ':' of "-w:foo" */
#endif
} else {
/* "-w...=foo" but not "-w[indowtype]=foo" */
goto loptbail;
}
} else {
goto loptbail;
}
if (!p || !*p) {
/* "-w[indowtype]" w/o '='/':' if there is a next element, use
it for "foo"; if not, supply a non-Null bogus value */
if (nextarg
&& (opttype == ArgValRequired || opttype == ArgValOptional))
p = nextarg, --(*argc_p), ++(*argv_p);
else if (opttype == ArgValRequired)
goto loptrequired;
else
p = ArgVal_novalue; /* there is no next element */
}
return p;
}
/* move argv[ndx] to end of argv[] array, then reduce argc to hide it;
prevents process_options() from encountering it after early_options()
has processed it; elements get reordered but all remain intact */
staticfn void
consume_arg(int ndx, int *ac_p, char ***av_p)
{
char *gone, **av = *av_p;
int i, ac = *ac_p;
/* "-one -two -three -four" -> "-two -three -four -one" */
if (ac > 2) {
gone = av[ndx];
for (i = ndx + 1; i < ac; ++i)
av[i - 1] = av[i];
av[ac - 1] = gone;
}
--(*ac_p);
}
/* consume two tokens for '-argname value' w/o '=' or ':' */
staticfn void
consume_two_args(int ndx, int *ac_p, char ***av_p)
{
/* when consuming "-two arg" from "-two arg -three -four",
the *ac_p manipulation results in "-three -four -two arg"
rather than the "-three -four arg -two" that would happen
with just two ordinary consume_arg() calls */
consume_arg(ndx, ac_p, av_p);
++(*ac_p); /* bring the final slot back into view */
consume_arg(ndx, ac_p, av_p);
--(*ac_p); /* take away restored slot */
}
/* process some command line arguments before loading options */
void
early_options(int *argc_p, char ***argv_p, char **hackdir_p)
{
char **argv, *arg, *origarg;
int argc, oldargc, ndx = 0, consumed = 0;
config_error_init(FALSE, "command line", FALSE);
/* treat "nethack ?" as a request for usage info; due to shell
processing, player likely has to use "nethack \?" or "nethack '?'"
[won't work if used as "nethack -dpath ?" or "nethack -d path ?"] */
if (*argc_p > 1 && !strcmp((*argv_p)[1], "?"))
opt_usage(*hackdir_p); /* doesn't return */
/*
* Both *argc_p and *argv_p account for the program name as (*argv_p)[0];
* local argc and argv implicitly discard that (by starting 'ndx' at 1).
* argcheck() doesn't mind, prscore() (via scores_only()) does (for the
* number of args it gets passed, not for the value of argv[0]).
*/
for (ndx = 1; ndx < *argc_p; ndx += (consumed ? 0 : 1)) {
consumed = 0;
argc = *argc_p - ndx;
argv = *argv_p + ndx;
arg = origarg = argv[0];
/* skip any args intended for deferred options */
if (*arg != '-')
continue;
/* allow second dash if arg name is longer than one character */
if (arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'
&& (arg[3] != '\0' && arg[3] != '=' && arg[3] != ':'))
++arg;
switch (arg[1]) { /* char after leading dash */
case 'b':
#ifdef CRASHREPORT
// --bidshow
if (argcheck(argc, argv, ARG_BIDSHOW) == 2) {
opt_terminate();
/*NOTREACHED*/
}
#endif
break;
case 'd':
if (argcheck(argc, argv, ARG_DEBUG) == 1) {
consume_arg(ndx, argc_p, argv_p), consumed = 1;
#ifndef NODUMPENUMS
} else if (argcheck(argc, argv, ARG_DUMPENUMS) == 2) {
opt_terminate();
/*NOTREACHED*/
#endif
} else if (argcheck(argc, argv, ARG_DUMPMONGEN) == 2) {
opt_terminate();
/*NOTREACHED*/
} else if (argcheck(argc, argv, ARG_DUMPWEIGHTS) == 2) {
opt_terminate();
/*NOTREACHED*/
} else {
#ifdef CHDIR
oldargc = argc;
arg = lopt(arg,
(ArgValRequired | ArgNamOneLetter | ArgErrSilent),
"-directory", origarg, &argc, &argv);
if (!arg)
error("Flag -d must be followed by a directory name.");
if (*arg != 'e') { /* avoid matching -decgraphics or -debug */
*hackdir_p = arg;
if (oldargc == argc)
consume_arg(ndx, argc_p, argv_p), consumed = 1;
else
consume_two_args(ndx, argc_p, argv_p), consumed = 2;
}
#endif /* CHDIR */
}
break;
case 'h':
case '?':
if (lopt(arg, ArgValDisallowed, "-help", origarg, &argc, &argv)
|| lopt(arg, ArgValDisallowed | ArgNamOneLetter, "-?",
origarg, &argc, &argv))
opt_usage(*hackdir_p); /* doesn't return */
break;
case 'n':
oldargc = argc;
if (!strcmp(arg, "-no-nethackrc")) /* no abbreviation allowed */
arg = nhStr("/dev/null");
else
arg = lopt(arg, (ArgValRequired | ArgErrComplain),
"-nethackrc", origarg, &argc, &argv);
if (arg) {
gc.cmdline_rcfile = dupstr(arg);
if (oldargc == argc)
consume_arg(ndx, argc_p, argv_p), consumed = 1;
else
consume_two_args(ndx, argc_p, argv_p), consumed = 2;
}
break;
case 's':
if (argcheck(argc, argv, ARG_SHOWPATHS) == 2) {
gd.deferred_showpaths = TRUE;
gd.deferred_showpaths_dir = *hackdir_p;
config_error_done();
return;
}
/* check for "-s" request to show scores */
if (lopt(arg,
((ArgValDisallowed | ArgErrComplain)
/* only accept one-letter if there is just one
dash; reject "--s" because prscore() via
scores_only() doesn't understand it */
| ((origarg[1] != '-') ? ArgNamOneLetter : 0)),
/* [ought to omit val-disallowed and accept
--scores=foo since -s foo and -sfoo are
allowed, but -s form can take more than one
space-separated argument and --scores=foo
isn't suited for that] */
"-scores", origarg, &argc, &argv)) {
/* at this point, argv[0] contains "-scores" or a leading
substring of it; prscore() (via scores_only()) expects
that to be in argv[1] so we adjust the pointer to make
that be the case; if there are any non-early args waiting
to be passed along to process_options(), the resulting
argv[0] will be one of those rather than the program
name but prscore() doesn't care */
scores_only(argc + 1, argv - 1, *hackdir_p);
/*NOTREACHED*/
}
break;
case 'u':
#if defined(UNIX)
if (lopt(arg, ArgValDisallowed, "-usage", origarg, &argc, &argv))
opt_usage(*hackdir_p);
#elif defined(WIN32) || defined(MSDOS) || defined(AMIGA)
if (arg[2]) {
(void) strncpy(svp.plname, arg + 2, sizeof(svp.plname) - 1);
} else if (ndx + 1 < *argc_p) {
const char *nextarg = (*argv_p)[ndx + 1];
if (nextarg[0] != '-') {
(void) strncpy(svp.plname, nextarg, sizeof(svp.plname) - 1);
} else {
raw_print("Player name expected after -u\n");
}
}
#endif
break;
case 'v':
if (argcheck(argc, argv, ARG_VERSION) == 2) {
opt_terminate();
/*NOTREACHED*/
}
break;
case 'w': /* windowtype: "-wfoo" or "-w[indowtype]=foo"
* or "-w[indowtype]:foo" or "-w[indowtype] foo" */
arg =
lopt(arg, (ArgValRequired | ArgNamOneLetter | ArgErrComplain),
"-windowtype", origarg, &argc, &argv);
if (gc.cmdline_windowsys)
free((genericptr_t) gc.cmdline_windowsys);
gc.cmdline_windowsys = arg ? dupstr(arg) : NULL;
break;
#if !defined(UNIX) && !defined(VMS)
case 'D':
wizard = TRUE, discover = FALSE;
break;
case 'X':
discover = TRUE, wizard = FALSE;
break;
#endif
default:
break;
}
}
/* empty or "N errors on command line" */
config_error_done();
return;
}
/* for command-line options that perform some immediate action and then
terminate the program without starting play, like 'nethack --version'
or 'nethack -s Zelda'; do some cleanup before that termination */
ATTRNORETURN staticfn void
opt_terminate(void)
{
program_state.early_options = 0;
config_error_done(); /* free memory allocated by config_error_init() */
nh_terminate(EXIT_SUCCESS);
/*NOTREACHED*/
}
ATTRNORETURN staticfn void
opt_usage(const char *hackdir)
{
#ifdef CHDIR
chdirx(hackdir, TRUE);
#else
nhUse(hackdir);
#endif
dlb_init();
genl_display_file(USAGEHELP, TRUE);
opt_terminate();
}
/* show the sysconf file name, playground directory, run-time configuration
file name, dumplog file name if applicable, and some other things */
ATTRNORETURN void
after_opt_showpaths(const char *dir)
{
#ifdef CHDIR
chdirx(dir, FALSE);
#else
nhUse(dir);
#endif
opt_terminate();
/*NOTREACHED*/
}
/* handle "-s <score options> [character-names]" to show all the entries
in the high scores file ('record') belonging to particular characters;
nethack will end after doing so without starting play */
ATTRNORETURN staticfn void
scores_only(int argc, char **argv, const char *dir)
{
/* do this now rather than waiting for final termination, in case there
is an error summary coming */
config_error_done();
#ifdef CHDIR
chdirx(dir, FALSE);
#else
nhUse(dir);
#endif
#ifdef SYSCF
iflags.initoptions_noterminate = TRUE;
initoptions(); /* sysconf options affect whether panictrace is enabled */
iflags.initoptions_noterminate = FALSE;
#endif
#ifdef PANICTRACE
ARGV0 = gh.hname; /* save for possible stack trace */
#ifndef NO_SIGNAL
panictrace_setsignals(TRUE);
#endif
#endif
#ifdef UNIX
(void) whoami(); /* set up default plname[] */
#endif
prscore(argc, argv);
nh_terminate(EXIT_SUCCESS); /* bypass opt_terminate() */
/*NOTREACHED*/
}
/*
* Returns: