mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-04 17:49:45 +02:00
480 lines
16 KiB
C
480 lines
16 KiB
C
/* cmdline.c -- command line parsing routines */
|
||
/* Copyright 1989 Carnegie Mellon University */
|
||
/*
|
||
* This module is designed to allow various modules to scan (and rescan)
|
||
* the command line for applicable arguments. The goal is to hide as
|
||
* much information about switches and their names as possible so that
|
||
* switches become more consistent across applications and so that the
|
||
* author of an application need not do a lot of work to provide numerous
|
||
* options. Instead, each module scans the command line for its own
|
||
* arguments.
|
||
*
|
||
* Command lines are of the following form:
|
||
* command -s1 -s2 opt2 -s3 arg1 arg2 -s4 opt4 arg3
|
||
* Note that there are three kinds of command line parameters:
|
||
* (1) A Switch is a "-" followed by a name, e.g. "-s1"
|
||
* (2) An Option is a Switch followed by a space and name, e.g. "-s2 opt2"
|
||
* (3) An Argument is a name by itself, e.g. "arg1"
|
||
* Note also that a switch followed by an argument looks just like an
|
||
* option, so a list of valid option names is necessary to disambiguate.
|
||
*
|
||
* Long names are good for readability, but single character abbreviations
|
||
* are nice for experienced users. cmdline.c allows single character
|
||
* abbreviations provided that they are unambiguous. These are
|
||
* recognized with no extra work by the programmer. If an
|
||
* isolated '?' is encountered in the command line, then all of
|
||
* the options and switches are printed as help and for debugging.
|
||
*
|
||
* Given that we must tell this module about option names and switch
|
||
* names, how should we do it? We can't wait until modules are
|
||
* initialized, since often modules want to read the command line
|
||
* at initialization time. In the original implementation, the
|
||
* main program was supposed to provide names for the whole program,
|
||
* but this violates modularity: when an option is added to a module,
|
||
* the main program has to be modified too. This is a real pain when
|
||
* different machines support different options and you want to have
|
||
* a single machine-independent main program. The solution is to
|
||
* have the main program import strings describing the options and
|
||
* switches used by each module. These are passed into cmdline.c
|
||
* before initialization of other modules is begun.
|
||
*
|
||
* A main program that uses cmdline.c should do the following:
|
||
* call cl_syntax(s) for each module's option/switch string.
|
||
* The string s should have the following format:
|
||
* "opt1<o>description;opt2<o>description;...;switch1<s>description;..."
|
||
* where opt1 and opt2 are option names (without the preceding "-"), and
|
||
* switch1 is a switch name. The <o> and <s> indicate whether the
|
||
* name is an option or a switch. The descriptions are arbitrary strings
|
||
* (without semicolons) that are printed out for the user when "?"
|
||
* is typed on the command line.
|
||
*
|
||
* After calling cl_syntax, main() should call
|
||
* cl_init(argv, argc)
|
||
* cl_init will report an error (to STDERR) if it finds any illegal
|
||
* switch or option names in argv, and help will be printed if "?"
|
||
* is found in argv. If cl_init returns false, then the user has been
|
||
* given an error message or help, and main should probably exit.
|
||
*
|
||
* Afterward, switches, options, and arguments can be accessed by
|
||
* calling cl_switch, cl_option, and cl_arg. If cl_switch or cl_option
|
||
* is called with a switch name that was not mentioned in the call to
|
||
* cl_init, an error will result. This indicates that the application
|
||
* author omitted a valid switch or option name when calling cl_init.
|
||
* This is an error because the full set of names is needed for error
|
||
* checking and to distinguish arguments from options.
|
||
*
|
||
*/
|
||
|
||
/*****************************************************************************
|
||
* Change Log
|
||
* Date | Change
|
||
*-----------+-----------------------------------------------------------------
|
||
* 13-Jun-86 | Created Change Log
|
||
* 6-Aug-86 | Modified for Lattice 3.0 -- use "void" to type some routines
|
||
* 20-Sep-89 | Redesigned the interface, adding cl_syntax call.
|
||
* 2-Apr-91 | JDW : further changes
|
||
* 27-Dec-93 | "@file" as first arg reads command line args from file
|
||
* 11-Mar-94 | PLu: Add private to cl_search() definition.
|
||
* 28-Apr-03 | DM: true->TRUE, false->FALSE
|
||
*****************************************************************************/
|
||
|
||
/* stdlib.h not on PMAX */
|
||
#ifndef mips
|
||
#include "stdlib.h"
|
||
#endif
|
||
#include "stdio.h"
|
||
#include "cext.h"
|
||
#include "userio.h"
|
||
#include "cmdline.h"
|
||
#include "ctype.h"
|
||
#include "string.h"
|
||
|
||
#define syntax_max 10 /* allow for 10 syntax strings */
|
||
private char *syntax[syntax_max];
|
||
private int n_syntax = 0; /* number of strings so far */
|
||
private char **argv; /* command line argument vector */
|
||
private int argc; /* length of argv */
|
||
|
||
private boolean cl_rdy = FALSE; /* set to TRUE when initialized */
|
||
|
||
#define cl_OPT 1
|
||
#define cl_SW 2
|
||
#define cl_INIT 3
|
||
#define cl_ARG 4
|
||
|
||
/*****************************************************************************
|
||
* Routines local to this module
|
||
*****************************************************************************/
|
||
private char *cl_search();
|
||
private int find_string();
|
||
private void indirect_command(char *filename, char *oldarg0);
|
||
private void ready_check();
|
||
|
||
/****************************************************************
|
||
* cl_arg
|
||
* Inputs:
|
||
* n: the index of the arg needed
|
||
* Results:
|
||
* pointer to the nth arg, or NULL if none exists
|
||
* arg 0 is the command name
|
||
*****************************************************************/
|
||
|
||
char *cl_arg(n)
|
||
int n;
|
||
{
|
||
return (n <= 0 ? argv[0] :
|
||
cl_search((char *)NULL, cl_ARG, n));
|
||
}
|
||
|
||
/* cl_help -- print help from syntax strings */
|
||
/**/
|
||
void cl_help()
|
||
{
|
||
register int i, j;
|
||
int count = 0; /* see if there are any switches or flags */
|
||
|
||
for (i = 0; i < n_syntax; i++) {
|
||
register char *ptr = syntax[i];
|
||
register char c = *ptr++;
|
||
while (c != EOS) {
|
||
while (c != EOS && !(isalnum(c))) c = *ptr++;
|
||
if (c != EOS) {
|
||
count++;
|
||
gprintf(TRANS, "-");
|
||
j = 1;
|
||
while (c != EOS && c != '<') {
|
||
gprintf(TRANS, "%c", c);
|
||
c = *ptr++;
|
||
j++;
|
||
}
|
||
if (c != EOS) {
|
||
c = *ptr++;
|
||
if (c == 'o') {
|
||
gprintf(TRANS, " xxx");
|
||
j += 4;
|
||
}
|
||
}
|
||
/* attempt to tab */
|
||
do {
|
||
gprintf(TRANS, " ");
|
||
} while (j++ < 16);
|
||
while (c != EOS && c != '>') c = *ptr++;
|
||
if (c != EOS) c = *ptr++;
|
||
while (c != EOS && c != ';') {
|
||
gprintf(TRANS, "%c", c);
|
||
c = *ptr++;
|
||
}
|
||
gprintf(TRANS, "\n");
|
||
}
|
||
}
|
||
}
|
||
if (!count) gprintf(TRANS, "No switches or options exist.\n");
|
||
}
|
||
|
||
/*****************************************************************************
|
||
* cl_init
|
||
* Inputs:
|
||
* char *switches[]: array of switch names
|
||
* int nsw: number of switch names
|
||
* char *options[]: array of option names
|
||
* int nopt: number of option names
|
||
* char *av: array of command line fields (argv)
|
||
* int ac: number of command line fields (argc)
|
||
* Effect:
|
||
* Checks that all command line entries are valid.
|
||
* Saves info for use by other routines.
|
||
* Returns:
|
||
* TRUE if syntax checks OK, otherwise false
|
||
*****************************************************************************/
|
||
|
||
boolean cl_init(av, ac)
|
||
char *av[];
|
||
int ac;
|
||
{
|
||
argv = av;
|
||
argc = ac;
|
||
|
||
/* check for help request */
|
||
if (argc == 2 && strcmp(argv[1], "?") == 0) {
|
||
cl_help();
|
||
return FALSE; /* avoid cl_search which would complain about "?" */
|
||
}
|
||
/* check for indirection */
|
||
if (argc == 2 && *(argv[1]) == '@') {
|
||
/* read new args from file */
|
||
indirect_command(av[1] + 1, av[0]);
|
||
}
|
||
/* check command line syntax: */
|
||
cl_rdy = TRUE;
|
||
return (cl_rdy = (cl_search("true", cl_INIT, 0) != NULL));
|
||
}
|
||
|
||
|
||
/****************************************************************
|
||
* cl_int_option
|
||
* Inputs:
|
||
* char *name: name of option
|
||
* long default: default value for option
|
||
* Result:
|
||
* returns long encoding of the option, deflt if none
|
||
* Implementation:
|
||
* call cl_option and sscanf result
|
||
*****************************************************************/
|
||
|
||
long cl_int_option(name, deflt)
|
||
char *name;
|
||
long deflt;
|
||
{
|
||
char *opt = cl_option(name);
|
||
if (opt) {
|
||
if (sscanf(opt, "%ld", &deflt) != 1) {
|
||
gprintf(TRANS, "Warning: option %s %s not an integer, ignored\n",
|
||
name, opt);
|
||
}
|
||
}
|
||
return deflt;
|
||
}
|
||
|
||
|
||
/****************************************************************
|
||
* cl_search
|
||
* Inputs:
|
||
* char *name: name of field, must be non-null if opt_sw == cl_INIT
|
||
* int opt_sw: option, switch, init, or arg
|
||
* int n: argument number (if opt_sw is cl_ARG)
|
||
* Result:
|
||
* returns pointer to option value/switch if one exists, otherwise null
|
||
* Implementation:
|
||
* parse the command line until name or arg is found
|
||
* see if the option is followed by a string that does
|
||
* not start with "-"
|
||
*****************************************************************/
|
||
|
||
private char *cl_search(name, opt_sw, n)
|
||
char *name;
|
||
int opt_sw;
|
||
int n; /* if opt_sw is cl_ARG, n > 0 tells which one */
|
||
{
|
||
register int i = 1; /* index into command line */
|
||
boolean abbr;
|
||
boolean result = TRUE;
|
||
|
||
ready_check();
|
||
|
||
/* parse command line: */
|
||
while (i < argc) {
|
||
register char *arg = argv[i];
|
||
/* arguments that start with '-' should be quoted and quotes must
|
||
be removed by the application
|
||
*/
|
||
if (*arg == '-') {
|
||
int arg_type = find_string(arg + 1, &abbr);
|
||
if (arg_type == cl_OPT) {
|
||
i += 1; /* skip name and option */
|
||
/* don't look for '-' because the option might be a
|
||
* negative number
|
||
*/
|
||
if (i >= argc /* || *arg == '-' */) {
|
||
if (opt_sw == cl_INIT) {
|
||
gprintf(ERROR, "missing argument after %s\n", arg);
|
||
result = FALSE;
|
||
}
|
||
} else if (opt_sw == cl_OPT &&
|
||
(strcmp(arg + 1, name) == 0 ||
|
||
(abbr && *(arg + 1) == name[0]))) {
|
||
return argv[i];
|
||
}
|
||
} else if (arg_type == cl_SW) {
|
||
if (opt_sw == cl_SW &&
|
||
(strcmp(arg + 1, name) == 0 ||
|
||
(abbr && *(arg + 1) == name[0])))
|
||
return arg;
|
||
} else if (opt_sw == cl_INIT) {
|
||
gprintf(ERROR, "invalid switch: %s\n", arg);
|
||
result = FALSE;
|
||
}
|
||
} else if (opt_sw == cl_ARG) {
|
||
if (n == 1) return arg;
|
||
n--;
|
||
}
|
||
i++; /* skip to next field */
|
||
}
|
||
if (opt_sw == cl_INIT) {
|
||
/* return name or NULL to represent TRUE or FALSE */
|
||
return (result ? name : NULL);
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/****************************************************************
|
||
* cl_option
|
||
* Inputs:
|
||
* char *name: option name
|
||
* Outputs:
|
||
* returns char *: the option string if found, otherwise null
|
||
****************************************************************/
|
||
|
||
char *cl_option(name)
|
||
char *name;
|
||
{
|
||
return cl_search(name, cl_OPT, 0);
|
||
}
|
||
|
||
/****************************************************************
|
||
* cl_switch
|
||
* Inputs:
|
||
* char *name: switch name
|
||
* Outputs:
|
||
* boolean: TRUE if switch found
|
||
****************************************************************/
|
||
|
||
boolean cl_switch(name)
|
||
char *name;
|
||
{
|
||
return (boolean)(cl_search(name, cl_SW, 0) != NULL);
|
||
}
|
||
|
||
/* cl_syntax -- install a string specifying options and switches */
|
||
/**/
|
||
boolean cl_syntax(char *s)
|
||
{
|
||
if (n_syntax < syntax_max) {
|
||
syntax[n_syntax++] = s;
|
||
return TRUE;
|
||
} else {
|
||
gprintf(ERROR, "cl_syntax: out of room\n");
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
/****************************************************************
|
||
* find_string
|
||
* Inputs:
|
||
* char *s: string to find, terminated by any non-alphanumeric
|
||
* boolean *abbr: set TRUE if s is an abbreviation, otherwise false
|
||
* Effect:
|
||
* Looks for s in syntax strings
|
||
* Returns:
|
||
* 0 = FALSE = not found, 1 = cl_OPT = option, 2 = cl_SW = switch
|
||
*****************************************************************/
|
||
|
||
private int find_string(s, abbr)
|
||
char *s;
|
||
boolean *abbr;
|
||
{
|
||
int found_it = FALSE;
|
||
int i;
|
||
*abbr = FALSE;
|
||
for (i = 0; i < n_syntax; i++) { /* loop through strings */
|
||
register char *syntax_ptr = syntax[i];
|
||
while (*syntax_ptr != EOS) {
|
||
register char *s_ptr = s;
|
||
while (*syntax_ptr != EOS &&
|
||
!(isalnum(*syntax_ptr))) syntax_ptr++;
|
||
while (*s_ptr != EOS && (*s_ptr++ == *syntax_ptr))
|
||
syntax_ptr++; /* only increment if there's a match */
|
||
if (!(isalnum(*s_ptr)) && *syntax_ptr == '<') {
|
||
syntax_ptr++; /* advance to the type field */
|
||
if (*syntax_ptr == 's') return cl_SW;
|
||
if (*syntax_ptr != 'o')
|
||
gprintf(ERROR,
|
||
"(internal error) bad cl_syntax string: %s\n",
|
||
syntax[i]);
|
||
return cl_OPT;
|
||
}
|
||
/* no match, so go to next */
|
||
while (*syntax_ptr != ';' && *syntax_ptr != EOS) syntax_ptr++;
|
||
if (*syntax_ptr == ';') syntax_ptr++;
|
||
}
|
||
}
|
||
|
||
/* no match, maybe there is a single character match */
|
||
if (s[0] == EOS || s[1] != EOS) return FALSE;
|
||
|
||
for (i = 0; i < n_syntax; i++) { /* loop through strings */
|
||
char *syntax_ptr = syntax[i];
|
||
while (*syntax_ptr != EOS) {
|
||
while (*syntax_ptr != EOS &&
|
||
!(isalnum(*syntax_ptr))) syntax_ptr++;
|
||
if (s[0] == *syntax_ptr) {
|
||
if (found_it) return FALSE; /* ambiguous */
|
||
/* else, find the type */
|
||
while (*syntax_ptr != '<' && *syntax_ptr != EOS)
|
||
syntax_ptr++;
|
||
syntax_ptr++;
|
||
if (*syntax_ptr == 's') found_it = cl_SW;
|
||
else if (*syntax_ptr == 'o') found_it = cl_OPT;
|
||
else return FALSE; /* error in string syntax */
|
||
}
|
||
/* no match, so go to next */
|
||
while (*syntax_ptr != ';' && *syntax_ptr != EOS) syntax_ptr++;
|
||
if (*syntax_ptr == ';') syntax_ptr++;
|
||
}
|
||
}
|
||
if (found_it) *abbr = TRUE;
|
||
return found_it;
|
||
}
|
||
|
||
|
||
/* get_arg -- get an argument from a file */
|
||
/**/
|
||
boolean get_arg(file, arg)
|
||
FILE *file;
|
||
char *arg;
|
||
{
|
||
int c;
|
||
while ((c = getc(file)) != EOF && isspace(c)) ;
|
||
if (c == EOF) return FALSE;
|
||
ungetc(c, file);
|
||
while ((c = getc(file)) != EOF && !isspace(c)) {
|
||
*arg++ = c;
|
||
}
|
||
*arg = 0;
|
||
return TRUE;
|
||
}
|
||
|
||
|
||
/* indirect_command -- get argv, argc from a file */
|
||
/**/
|
||
private void indirect_command(filename, oldarg0)
|
||
char *filename;
|
||
char *oldarg0;
|
||
{
|
||
FILE *argfile = fopen(filename, "r");
|
||
if (!argfile) {
|
||
argv = (char **) malloc(sizeof(char *));
|
||
argv[0] = oldarg0;
|
||
argc = 1;
|
||
} else {
|
||
int i = 1;
|
||
char arg[100];
|
||
while (get_arg(argfile, arg)) i++;
|
||
fclose(argfile);
|
||
argfile = fopen(filename, "r");
|
||
argv = (char **) malloc(sizeof(char *) * i);
|
||
argv[0] = oldarg0;
|
||
argc = i;
|
||
i = 1;
|
||
while (get_arg(argfile, arg)) {
|
||
argv[i] = (char *) malloc(strlen(arg) + 1);
|
||
strcpy(argv[i], arg);
|
||
i++;
|
||
}
|
||
fclose(argfile);
|
||
}
|
||
}
|
||
|
||
/****************************************************************
|
||
* ready_check
|
||
* Effect:
|
||
* Halt program if cl_rdy is not true.
|
||
*****************************************************************/
|
||
private void ready_check()
|
||
{
|
||
if (!cl_rdy) {
|
||
gprintf(ERROR,
|
||
"Internal error: cl_init was not called, see cmdline.c\n");
|
||
EXIT(1);
|
||
}
|
||
}
|