Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.7 - Better arg parsing #71

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
may 2024
- 1.7
- Heavily refactored arg parsing to use a simple search-and-dispatch pattern
- The argument order no longer matters. The last filename entered is the one used.
- longopts can now take arguments in --key=value or --key value format
- Added -c to --color
- -h/--help is now a canon option
- -- now disables opt parsing instead of merely exiting the arg parsing loop
- updated man page to explain what -s/--sector does
- made an effort to keep code style as similar to pixel's as possible (next line brace, 2-space indent)

april 2022
- 1.6
- configure script must error-out when (n)curses isn't found
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ AC_INIT
AC_CONFIG_SRCDIR([hexedit.c])
AC_CONFIG_HEADERS(config.h)

define(TheVERSION, 1.6)
define(TheVERSION, 1.7)

PRODUCT=hexedit
VERSION=TheVERSION
Expand Down
224 changes: 181 additions & 43 deletions hexedit.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,182 @@ const modeParams modes[LAST] = {
modeType mode = maximized;
int colored = FALSE;
int isReadOnly = FALSE;
int disableOpts = FALSE;

const char * const usage = "usage: %s [-s | --sector] [-m | --maximize] [-l<n> | --linelength <n>] [-r | --readonly]"
#ifdef HAVE_COLORS
" [--color]"
" [-c | --color]"
#endif
" [-h | --help] filename\n";

typedef void (*ParseArgCallbackFn)(const char *nextArg);

static void pacallback_sector (const char *unused);
static void pacallback_readOnly (const char *unused);
static void pacallback_maximize (const char *unused);
#ifdef HAVE_COLORS
static void pacallback_color (const char *unused);
#endif
static void pacallback_lineLength (const char *nextArg);
static void pacallback_disableOpts (const char *unused);
static void pacallback_usage (const char *unused);

static inline int parseArg_matchOpt (const char *arg, const char *shortName, const char *longName);
static inline int parseArg (const char **argv);

/* name: pacallback_*
* nextArg: argument for option, if applicable
* desc: these are callbacks per ParseArgCallbackFn, used by parseArg
*/
static void pacallback_sector(const char *unused)
{
(void) unused;
mode = bySector;
}

static void pacallback_readOnly(const char *unused)
{
(void) unused;
isReadOnly = TRUE;
}

static void pacallback_maximize(const char *unused)
{
(void) unused;
mode = maximized;
lineLength = 0;
}

#ifdef HAVE_COLORS
static void pacallback_color(const char *unused)
{
(void) unused;
colored = TRUE;
}
#endif

static void pacallback_lineLength(const char *nextArg)
{
lineLength = atoi(nextArg);

if (lineLength < 0 || lineLength > 4096)
DIE("%s: illegal line length\n")
}

static void pacallback_disableOpts(const char *unused)
{
(void) unused;
disableOpts = TRUE;
}

static void pacallback_usage(const char *unused)
{
(void) unused;
DIE(usage);
}

/* name: parseArg_matchOpt
* arg: current argument
* shortName: short name to check against
* longName: long name to check against
* return: number indicating status
* 0: no match
* 1: short match
* 2: long match
*/
static inline int parseArg_matchOpt(const char *arg, const char *shortName, const char *longName)
{
if (shortName != NULL && strbeginswith(arg, shortName))
return 1;

if (longName != NULL && strbeginswith(arg, longName))
return 2;

return 0;
}

/* name: parseArg
* argvp: pointer to current argv offset
* return: number of arguments to advance
* desc: parses arguments according to embedded argument list.
* Execution is handled by callback functions.
* notes:
*
* 1. This can be canonized as a type in a future update, but is left
* anonymous for now
*
* 2. Adds support for the --longopt=value convention
*
* 3. if a filename was already selected, free the existing one and
* replace it. Note that file scope variables are guaranteed to be
* zero initialized per the standard.
*
* 4. Fixes issue #70 -- if no options match the argument, it is assumed
* to be the filename. The effect is that the most recent "naked"
* argument is used as the filename.
*/
static inline int parseArg(const char **argvp)
{
static const struct { // 1
const char * shortName;
const char * longName;
int hasArg;
ParseArgCallbackFn callback;
} opts[] = {
{"-s", "--sector", FALSE, pacallback_sector},
{"-r", "--readonly", FALSE, pacallback_readOnly},
{"-m", "--maximize", FALSE, pacallback_maximize},
#ifdef HAVE_COLORS
{"-c", "--color", FALSE, pacallback_color},
#endif
{"-l", "--linelength", TRUE, pacallback_lineLength},
{"-h", "--help", FALSE, pacallback_usage},
{"--", NULL, FALSE, pacallback_disableOpts},
{"-", NULL, FALSE, pacallback_usage},
};
static const size_t opts_len = sizeof opts / sizeof *opts;

const char *curArg = argvp[0];
const char *nextArg = NULL;
int advanceBy = 1;

if (disableOpts)
goto exit_disableOpts;

for (size_t i = 0; i < opts_len; ++i)
{
int matchOpt = parseArg_matchOpt(curArg, opts[i].shortName, opts[i].longName);
const char *strchrMatch;

if (matchOpt == 0)
continue;

if (opts[i].hasArg)
{
if (matchOpt == 1 && strlen(curArg) > 2)
nextArg = curArg + 2;

else if (matchOpt == 2 && (strchrMatch = strchr(curArg, '=')) != NULL) // 2
nextArg = strchrMatch + 1;

else
{
nextArg = argvp[1];
advanceBy = 2;
}
}

opts[i].callback(nextArg);
return advanceBy;
}

exit_disableOpts:
if (fileName != NULL) // 3
free(fileName);

fileName = strdup(curArg); // 4
return 1;
}

/*******************************************************************************/
/* main */
Expand All @@ -57,52 +226,21 @@ int main(int argc, char **argv)
progName = basename(argv[0]);
argv++; argc--;

for (; argc > 0; argv++, argc--)
{
if (streq(*argv, "-s") || streq(*argv, "--sector"))
mode = bySector;
else if (streq(*argv, "-r") || streq(*argv, "--readonly"))
isReadOnly = TRUE;
else if (streq(*argv, "-m") || streq(*argv, "--maximize")) {
mode = maximized;
lineLength = 0;
}
#ifdef HAVE_COLORS
else if (streq(*argv, "--color"))
colored = TRUE;
#endif
else if (strbeginswith(*argv, "-l") || strbeginswith(*argv, "--linelength")) {
if (strbeginswith(*argv, "-l") && strlen(*argv) > 2)
lineLength = atoi(*argv + 2);
else {
argv++; argc--;
lineLength = atoi(*argv);
}
if (lineLength < 0 || lineLength > 4096)
DIE("%s: illegal line length\n")
} else if (streq(*argv, "--")) {
argv++; argc--;
break;
} else if (*argv[0] == '-')

DIE(usage)
else break;
}
if (argc > 1) DIE(usage);
for (; argc > 0;)
{
int advanceBy = parseArg((const char **)argv);

init();
if (argc == 1) {
fileName = strdup(*argv);
openFile();
argv += advanceBy;
argc -= advanceBy;
}

init();
initCurses();
if (fileName == NULL) {
if (!findFile()) {
exitCurses();
DIE("%s: No such file\n");
}
openFile();
if (fileName == NULL && !findFile()) {
exitCurses();
DIE("%s: No such file\n");
}
openFile();
readFile();
do display();
while (key_to_function(getch()));
Expand Down